Golang Slice
程序员文章站
2024-03-01 13:29:16
...
Slice介绍
Slice也叫做切片,是golang中最为常用的一个结构之一,跟数组相比,它更加灵活便利,拥有自动扩容策略,但是也存在着一些比较容易被忽略的坑点,文章会先介绍Slice的基本使用方式,接着会介绍Slice的内部实现,最后会总结一些我遇到过的坑点(这一点可能会持续更新)~
使用方式
Slice切片初始化
slice的初始化和数组的初始化方式特别相似,但是这是两种完全不同的数据结构,数组的容量大小是不允许被调整的。
//slice的初始化
s2 := []int{}
s3 := []int{1, 2, 3}
s4 := make([]int, 0, 10) //预分配一些容量,make的第二个参数为len,第三个参数为capacity
//数组初始化
s5 := [3]int{1, 2, 3}
s6 := [...]int{1, 2, 3} //自动推导
基本操作
s1 := make([]int, 10) //make时指定len时会给定默认值,如果打印s1,输出结果是10个0,感兴趣可以试一试
//slice的遍历
for index, value := range s1 {
//do something
}
//slice追加
s1 := append(s1, 1)
//清空slice
s1 := s1[0:0]
//获取slice的长度
length := len(s1)
//获取slice的容量
capacity := cap(s1)
//获取slice子串
s2 := s1[0:10] //这里是左闭右开的原则
//将s1或s1的字串追加到s2中
s2 := append(s2, s1[0:10]...) //...是一个操作符,会将整个切片拆开append到s2中
slice的内部结构
在运行时切片的结构使用如下SliceHeader表示的,分别有指向数组的指针、切片长度、切片容量
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-k4r5GrYQ-1594266842810)(https://yunpan.oa.tencent.com/note/api/file/getImage?fileId=5f06916e6f0b9316e2670201)]
type SliceHeader struct {
Data uintptr //指向数组的指针
Len int //切片长度
Cap int //切片容量
}
slice自动扩容机制
- 如果期望容量大于当前容量的两倍就会使用期望容量;
- 如果当前切片容量小于 1024 就会将容量翻倍;
- 如果当前切片容量大于 1024 就会每次增加 25% 的容量,直到新容量大于期望容量;
坑点
- Slice并没有提供官方的函数可供查询某元素是否存在,所以只能手动遍历Slice;
- 在使用Slice时如果没有预估容量,可能会带来很大的扩容开销,如果是在高并发场景下,一定要记得预估capacity并提前预创建
- 删除一个Slice中的元素是一件比较麻烦的事情
- Slice赋值为浅拷贝,这一点特别值得注意。在没有扩容的情况下,复制后的切片和原始切片指向的数组将是同一个,操作两者都会操作同一个底层数组,并改变自己的len和cap,而另一个切片的结构信息不会改变;有扩容的情况下,复制后的切片会指向一个新的数组,gc不会将原来的数组清理掉。
Slice赋值问题
package main
import "fmt"
func main() {
data := make(map[string][]int32)
item0 := make([]int32, 0)
item0 = append(item0, 1)
item0 = append(item0, 2)
data["item0"] = item0
// 获取到item0, 并向其中添加两项
temp := data["item0"]
temp = append(temp, 3)
temp = append(temp, 4)
// 输出结果是:temp len is 4, data["item0"] len is 2
// 这里返回的temp,是一个data["item0"]的副本,内部指向同一个底层数组
//其实此时他们已经指向不同的底层数组了,因为原始的cap只有2(可以推断),再append元素将自动扩容到4,temp也会指向不同的地址;其实很容易想到,在这一点上可能存在一些隐患
fmt.Printf("temp len is %v, data[\"item0\"] len is %v\n", len(temp), len(data["item0"]))
}