写点什么

golang 中的切片

作者:六月的
  • 2022-10-20
    上海
  • 本文字数:2721 字

    阅读完需:约 1 分钟

golang中的切片

索引:https://waterflow.link/articles/1666277946416


在 go 中切片的底层是数组,所以切片的数据连续存储在数组的数据结构中。如果底层的数组满了,切片还需要添加元素的话,底层数组就需要扩容。如果底层数组几乎为空时,就会缩容。


在切片内部其包含一个指向底部数组的指针、切片的长度、切片的容量。长度是指切片包含的元素树,容量底层数组中的元素数。


我们先看个例子:


s := make([]int, 2, 4)
复制代码


我们初始化一个长度为 2,容量为 4 的切片。长度是必选的,容量可选。当不指定容量时,容量和长度相同,也就是底层数组的长度。



因为长度为 2,所以 go 只初始化了 2 个元素。我们可以打印下看下结果:


package main
import "fmt"
func main() { s := make([]int, 2, 4) fmt.Printf("s:%v,len(s):%d,cap(s):%d\n", s, len(s), cap(s))}
复制代码


go run 4.gos:[0 0],len(s):2,cap(s):4
复制代码


下面我们把 s[1]设置为 1,所以 s 的第二个元素会被更新为 1,s 的长度和容量不会变化



我们看下执行结果:


package main
import "fmt"
func main() { s := make([]int, 2, 4) s[1] = 1 fmt.Printf("s:%v,len(s):%d,cap(s):%d\n", s, len(s), cap(s))}
复制代码


go run 4.gos:[0 1],len(s):2,cap(s):4
复制代码


但是当我们设置 s[2] = 1时会报错,像下面这样:


go run 4.gopanic: runtime error: index out of range [2] with length 2
复制代码


但是我们的容量是 4,也就是底层数组还有 2 个空闲元素。那我们该如何给剩下的元素赋值呢?


我们可以使用 go 的内置函数 append,他可以将元素追加到切片的末尾。它可以追加一个元素到切片末尾:


s = append(s, 1)
复制代码


也可以追加多个元素到切片末尾:


s = append(s, 1, 2)s = append(s, []int{1, 2}...)
复制代码


还有一种比较特殊的情况,将字符串追加到字节切片末尾:


s = append([]byte("hello"), "world"...)
复制代码


现在我们执行 append(s, 2),将元素 2 追加到 s[1]后面,我们会得到下面的结果



切片的长度变成了 3,容量还是 4:


package main
import "fmt"
func main() { s := make([]int, 2, 4) s[1] = 1 s = append(s, 2) fmt.Printf("s:%v,len(s):%d,cap(s):%d\n", s, len(s), cap(s))}
复制代码


go run 4.gos:[0 1 2],len(s):3,cap(s):4
复制代码


现在我们如果直接添加 3 个元素进去,像下面这样:


s = append(s, 1, 2, 3)
复制代码


我们会得到下面的结果:


package main
import "fmt"
func main() { s := make([]int, 2, 4) s[1] = 1 s = append(s, 2) s = append(s, 1, 2, 3) fmt.Printf("s:%v,len(s):%d,cap(s):%d\n", s, len(s), cap(s))}
复制代码


go run 4.gos:[0 1 2 1 2 3],len(s):6,cap(s):8
复制代码


我们发现现在元素的长度变成了 6 个,这个很好理解,因为我们总共添加了 6 个元素。但是切片的容量为什么会是 8 呢?


这是因为当我们添加第 5 个元素时,发现已经超过切片的容量了。这个时候会触发扩容,会将容量加倍,然后复制所有元素创建另一个数组。然后会把剩下的 2 和 3 插进去。



扩容机制:如果不到 1024 个元素,会成倍扩容;超过 1024 个元素,按 25%扩容


切片中还提供了一种半开区间的赋值方式,保留第一个索引,排除第二个索引,像下面这样:


package main
import "fmt"
func main() { s := make([]int, 2, 4) s[1] = 1 s = append(s, 2) s = append(s, 1, 2, 3) fmt.Printf("s:%v,len(s):%d,cap(s):%d\n", s, len(s), cap(s)) s2 := s[1:2] // 这里重新赋值给s2 fmt.Printf("s2:%v,len(s2):%d,cap(s2):%d\n", s2, len(s2), cap(s2))}
复制代码


go run 4.gos:[0 1 2 1 2 3],len(s):6,cap(s):8s2:[1],len(s2):1,cap(s2):7
复制代码


切片 s 和 s2 此时是引用同一个底层数组的,但是由于 s2 是从 1 开始,所以容量变成了 7



这时如果我们修改了 s2[0]或者 s[1],实际上他们指向的是底层数组的同一个元素。所以 s2[0]或者 s[1]都会被修改掉。


package main
import "fmt"
func main() { s := make([]int, 2, 4) s[1] = 1 s = append(s, 2) s = append(s, 1, 2, 3) s2 := s[1:2] s2[0] = 8 // 这里 fmt.Printf("修改俩切片指向的同一个元素:s:%v,len(s):%d,cap(s):%d)----s2:%v,len(s2):%d,cap(s2):%d\n", s, len(s), cap(s), s2, len(s2), cap(s2))}
复制代码


go run 4.go修改俩切片指向的同一个元素:s:[0 8 2 1 2 3],len(s):6,cap(s):8)----s2:[8],len(s2):1,cap(s2):7
复制代码


接着我们继续往 s2 中插入一个元素,看看会发生什么:


package main
import "fmt"
func main() { s := make([]int, 2, 4) s[1] = 1 s = append(s, 2) s = append(s, 1, 2, 3) s2 := s[1:2] s2[0] = 8 // 插入元素6 s2 = append(s2, 6) fmt.Printf("往s2插入一个元素:s:%v,len(s):%d,cap(s):%d----s2:%v,len(s2):%d,cap(s2):%d\n", s, len(s), cap(s), s2, len(s2), cap(s2))}
复制代码


go run 4.go往s2插入一个元素:s:[0 8 6 1 2 3],len(s):6,cap(s):8----s2:[8 6],len(s2):2,cap(s2):7
复制代码


我们可以看到 s2[1]的元素写进去了,长度变为 2。但是 s2 的长度并没有变化,s2[2]的元素却被修改为了 6。这是因为往 s2 插入元素时并没有超过 s2 的容量,所以还是共用同一个底层数组。


那现在我们继续往 s2 中添加 6 个元素,看看超出容量后的底层数组会是什么样的:


package main
import "fmt"
func main() { s := make([]int, 2, 4) s[1] = 1 s = append(s, 2) s = append(s, 1, 2, 3) s2 := s[1:2] s2[0] = 8 s2 = append(s2, 6) // 继续插入6个元素 s2 = append(s2, 7) s2 = append(s2, 8) s2 = append(s2, 9) s2 = append(s2, 10) s2 = append(s2, 11) s2 = append(s2, 12) fmt.Printf("继续往s2插入6个元素:s:%v,len(s):%d,cap(s):%d----s2:%v,len(s2):%d,cap(s2):%d\n", s, len(s), cap(s), s2, len(s2), cap(s2))}
复制代码


go run 4.go继续往s2插入6个元素:s:[0 8 6 7 8 9],len(s):6,cap(s):8----s2:[8 6 7 8 9 10 11 12],len(s2):8,cap(s2):14
复制代码


我们来分析下上面的例子:


  1. 当我们往 s2 插入 7 的时候,此时 s2 的长度变为 3,容量还是 7。s[3]对应也被修改。

  2. 当我们往 s2 插入 8 的时候,此时 s2 的长度变为 4,容量还是 7。s[4]对应也被修改。

  3. 当我们往 s2 插入 9 的时候,此时 s2 的长度变为 5,容量还是 7。s[5]对应也被修改。

  4. 当我们往 s2 插入 10 的时候,此时 s2 的长度变为 6,容量还是 7。s[6]对应也被修改。

  5. 当我们往 s2 插入 11 的时候,此时 s2 的长度变为 7,容量还是 7。因为 s 切片长度为 6,所以没有变化。

  6. 当我们往 s2 插入 12 的时候,此时 s2 超过 s2 的容量引发扩容,底层数组被复制,s2 指向一个新的容量为 14 的数组。因为 s 长度小于容量,所以还是指向原来的数组。


我们可以通过下面的图片,增加更清晰的认识。



用户头像

六月的

关注

还未添加个人签名 2019-07-23 加入

还未添加个人简介

评论

发布
暂无评论
golang中的切片_Go_六月的_InfoQ写作社区