Go数据结构系列之 Array and Alice

简介: Go数据结构系列之 Array and Alice

概述


在使用 Go 开发的时候,数组和切片经常被使用到,这篇文章来简单聊聊吧。


数组 array


在 Go 中,有两种方式可以初始化数组

func main() {
  userId := [3]int{1, 2, 3}
  userName := [...]string{"wqq", "curry", "joke"}
}

一种是显式的定义数组的大小,另一种通过 […] 声明数组,Go 会在编译期间推导出数组的大小。


既然使用了数组,少不了遍历,在 Go 中遍历数组一般也就两种方式。

func main() {
  userIds := [3]int{1, 2, 3}
  names := [...]string{"wqq", "curry", "joke"}
  for i := 0; i < len(names); i++ {
    fmt.Printf("user id is:%v,user name is:%v\n", userIds[i], names[i])
  }
  for index, item := range names {
    fmt.Printf("user id is:%v,user name is:%v\n", userIds[index], item)
  }
}

第一种就是你所认知的 for 循环。第二种可以使用 for/range 表达式,该表达式返回两个值,第一个值是索引,第二个值对应此索引的元素值。range 不单单能遍历数组,还能遍历 slice、map、channel 等集合结构。当然这些不在这篇文章的讨论范围内。


切片 slice


切片本质上是动态数组,它的底层包含了对数组的引用。切片的长度是动态的,可以随意的对其进行 append 操作,在使用的过程中,如果容量不足,会自动进行扩容操作。我们可以从源码看看 slice 的结构。源码位于 src/runtime/slice.go ,更多底层知识可以自行查看源码。

type slice struct {
  array unsafe.Pointer // 底层数组的指针位置
  len   int // 切片当前长度
  cap   int //容量,当容量不够时,会触发动态扩容的机制
}

同理,初始化 slice 的方式也是多样的。


使用 make 关键字


和数组一样,使用字面量初始化


通过下标的方式获取数组或者切片的一部分,生成 slice

func main() {
  // 字面量初始化
  userIds1 := []int{1, 2, 3}
  // make初始化slice的长度为5,容量为10
  userIds2 := make([]int, 5,10)
  // 通过下标的方式获取数组的一部分作为alice
  userArray := [5]string{"curry", "wqq", "lisa", "tony", "james"}
  // 获取从索引下标0开始,到下标3(不包括3)
  user := userArray[0:3]
  fmt.Printf("userIds1:%v,userIds2:%v,userSlice:%v\n", userIds1, userIds2, user)
}

这里就拿 make 初始化切片进行说明。


1668567813776.jpg


 注:图片来源 《Go编程专家》

这段初始化操作表示 slice 的长度是 5,容量是 10,array 字段存储的是引用数组的指针位置。因为长度是 5,我们可以使用下标 0-4 来操作此 slice。同时容量是 10,所以后续向 slice 添加新数据暂时不需要重新分配新内存。


那数组和切片有什么关联呢?


我们看看通过下标的方式获取数组数据,初始化切片的一种形式。

func main() {
  userArray := [4]string{"curry", "wqq", "lisa", "tony"}
  // 获取从索引下标0开始,到下标3(不包括3)
  userSlice := userArray[0:3]
  userSlice[0] = "zhangsan"
  fmt.Printf("userArray:%v,userSlice:%v\n", userArray, userSlice)
}

我们用数组创建了 userSlice 的切片,此时 userSlice 将和 userArray 共用一部分内存。因此在修改 userSlice 索引 0 处的值时,操作的是同一块数组内存地址,从结果中可以看出生效了。


1668567836307.jpg


然后我们开始往 userSlice 切片添加元素。

func main() {
  userArray := [4]string{"curry", "wqq", "lisa", "tony"}
  // 获取从索引下标0开始,到下标3(不包括3)
  userSlice := userArray[0:3]
  userSlice[0] = "zhangsan"
  fmt.Printf("userArray:%v,userSlice:%v\n", userArray, userSlice)
  userSlice = append(userSlice, "test1")
  fmt.Printf("userArray:%v,userSlice:%v\n", userArray, userSlice)
}

查看输出结果:


1668567850316.jpg


可以看到,再向 userSlice 增加一个元素后,打印结果,数组和切片值一样,操作之后 userSlice 的 len 是 4,数组的长度也是 4。操作 append 后 userSlice 底层数组和 userArray 指向的还是同一个内存地址,并不需要发生扩容。


这时候,userSlice 所引用的底层数组已经满了 (底层数组的长度是 4),我们继续向 userSlice 增加元素。

func main() {
  userArray := [4]string{"curry", "wqq", "lisa", "tony"}
  // 获取从索引下标0开始,到下标3(不包括3)
  userSlice := userArray[0:3]
  userSlice[0] = "zhangsan"
  fmt.Printf("userArray:%v,userSlice:%v\n", userArray, userSlice)
  userSlice = append(userSlice, "test1")
  fmt.Printf("userArray:%v,userSlice:%v\n", userArray, userSlice)
  userSlice = append(userSlice, "test2")
  fmt.Printf("userArray:%v,user:%v\n", userArray, userSlice)
 }

查看输出结果:


1668567864159.jpg


可以看到,userArray 的元素未变,因为这时候 userSlice 切片的长度已经大于原指向的数组的长度了, userSlice 发生了扩容。


我们可以做个实验测试一下,我们修改数组 userArray 范围内的 userSlice 元素的值,查看数组的数据是否会跟着改变。

func main() {
  userArray := [4]string{"curry", "wqq", "lisa", "tony"}
  // 获取从索引下标0开始,到下标3(不包括3)
  userSlice := userArray[0:3]
  userSlice[0] = "zhangsan"
  fmt.Printf("userArray:%v,userSlice:%v\n", userArray, userSlice)
  userSlice = append(userSlice, "test1")
  fmt.Printf("userArray:%v,userSlice:%v\n", userArray, userSlice)
  userSlice = append(userSlice, "test2")
  fmt.Printf("userArray:%v,userSlice:%v\n", userArray, userSlice)
// 改变索引0处的值
  userSlice[0] = "only one"
  fmt.Printf("userArray:%v,userSlice:%v\n", userArray, userSlice)
}


最后一行已经说明了一切。此时的 userSlice 发生了扩容,不再和 userArray 共用原数组空间了。因此对 userSlice 的改动不会影响到 userArray。


关于扩容


前面提到在向切片添加新元素时如果此时切片的容量不足,会自动发生扩容。所谓扩容,也就是为当前切片生成新的一块内存空间,然后根据一定规则,将原切片的元素全部拷贝到新的地址。扩容的规则在 src/runtime/slice.go 里的 growslice 方法。


1668567898583.jpg


这里截取了此方法中关于扩容规则的代码。


如果期望的新容量 (cap) 大于当前容量的两倍,那么就直接使用期望的容量


如果当前切片的长度 (len) 小于 1024,那么把当前容量翻倍


如果当前切片的长度 (len) 大于等于 1024,那么每次把当前容量增加 1/4,直到新容量值大于期望的的容量。


其实要写下去还有很多东西,比如,sliceCopy、底层编译逻辑……,有些东西我也没看过,学习的最好方式还是自己动手然后输出。


相关文章
|
Go
《Go 简易速速上手小册》第3章:数据结构(2024 最新版)(下)
《Go 简易速速上手小册》第3章:数据结构(2024 最新版)(下)
77 1
|
存储 供应链 数据可视化
《Go 简易速速上手小册》第3章:数据结构(2024 最新版)(上)
《Go 简易速速上手小册》第3章:数据结构(2024 最新版)(上)
134 1
|
1月前
|
消息中间件 缓存 NoSQL
Redis各类数据结构详细介绍及其在Go语言Gin框架下实践应用
这只是利用Go语言和Gin框架与Redis交互最基础部分展示;根据具体业务需求可能需要更复杂查询、事务处理或订阅发布功能实现更多高级特性应用场景。
201 86
|
3月前
|
存储 监控 算法
公司员工泄密防护体系中跳表数据结构及其 Go 语言算法的应用研究
在数字化办公中,企业面临员工泄密风险。本文探讨使用跳表(Skip List)数据结构优化泄密防护系统,提升敏感数据监测效率。跳表以其高效的动态数据处理能力,为企业信息安全管理提供了可靠技术支持。
89 0
|
存储 Go 容器
深入探究Go语言中的数据结构
深入探究Go语言中的数据结构
251 3
|
10月前
|
存储 Go 索引
go语言中的数组(Array)
go语言中的数组(Array)
208 67
|
9月前
|
存储 安全 Go
Go语言中的map数据结构是如何实现的?
Go 语言中的 `map` 是基于哈希表实现的键值对数据结构,支持快速查找、插入和删除操作。其原理涉及哈希函数、桶(Bucket)、动态扩容和哈希冲突处理等关键机制,平均时间复杂度为 O(1)。为了确保线程安全,Go 提供了 `sync.Map` 类型,通过分段锁实现并发访问的安全性。示例代码展示了如何使用自定义结构体和切片模拟 `map` 功能,以及如何使用 `sync.Map` 进行线程安全的操作。
251 9
|
存储 应用服务中间件 nginx
Nginx入门 -- 基本数据结构中之ngx_str_t,ngx_array_t
Nginx入门 -- 基本数据结构中之ngx_str_t,ngx_array_t
219 1
|
搜索推荐 算法 Go
深入探索堆:Go语言中的高效数据结构
深入探索堆:Go语言中的高效数据结构
|
应用服务中间件 nginx C语言
Nginx入门 -- 基本数据结构中之ngx_str_t,ngx_array_t
这两种数据结构是Nginx自定义数据类型的例子,它们证明了Nginx设计者在构建一个为高并发和高性能优化的web服务器时的精确和高效。理解这些数据结构是深入学习Nginx内部机制的基础,同时也是扩展和开发Nginx模块不可或缺的一部分知识。
121 1

热门文章

最新文章

  • 1
    PHP 数组查找:为什么 `isset()` 比 `in_array()` 快得多?
    105
  • 2
    Java 中数组Array和列表List的转换
    408
  • 3
    JavaScript中通过array.map()实现数据转换、创建派生数组、异步数据流处理、复杂API请求、DOM操作、搜索和过滤等,array.map()的使用详解(附实际应用代码)
    460
  • 4
    通过array.reduce()实现数据汇总、条件筛选和映射、对象属性的扁平化、转换数据格式、聚合统计、处理树结构数据和性能优化,reduce()的使用详解(附实际应用代码)
    1064
  • 5
    通过array.some()实现权限检查、表单验证、库存管理、内容审查和数据处理;js数组元素检查的方法,some()的使用详解,array.some与array.every的区别(附实际应用代码)
    299
  • 6
    通过array.every()实现数据验证、权限检查和一致性检查;js数组元素检查的方法,every()的使用详解,array.some与array.every的区别(附实际应用代码)
    180
  • 7
    多维数组操作,不要再用遍历循环foreach了!来试试数组展平的小妙招!array.flat()用法与array.flatMap() 用法及二者差异详解
    120
  • 8
    别再用双层遍历循环来做新旧数组对比,寻找新增元素了!使用array.includes和Set来提升代码可读性
    120
  • 9
    Array.forEach实战详解:简化循环与增强代码可读性;Array.forEach怎么用;面对大量数据时怎么提高Array.forEach的性能
    99
  • 10
    深入理解 JavaScript 中的 Array.find() 方法:原理、性能优势与实用案例详解
    314