目录
一、切片原理
1.1数组与切片
数组是具有固定长度且拥有零个或者多个相同数据类型元素的序列。 切片是一个拥有相同类型元素的可变长度的序列。切片是“动态数组”。
数组的缺点:
1.无法添加,删除元素。
2.长度很大时,传递会耗费资源(可以传递数组指针解决)。
切片的优点:
1.不用预设长度,可以添加删除元素。
2.自动扩容。
1.2切片的结构
运行时结构(源码位置:src/runtime/slice.go)
array指针指向底层数组,len表示切片长度,cap表示切片容量。 理解起来就是,切片本身只是一个结构体,有指向数组的指针,长度和容量组成。
1.3切片的创建
1.4切片的重组
1.4.1切片重组表达式
简单表达式:a[low:high]
扩展表达式:a[low:high:max] 不常用(基本不使用)
1.4.2简单表达式
切取数组时 0<=low<=high<=len() 切取切片时 0<=low<=high<=cap()
“小坑” 一般情况下,切取都是使用底层数组,但是当low=cap时,返回空切片。即如图:
切片扩容
Go 语言根据切片的当前容量选择不同的策略进行扩容:
如果期望容量大于当前容量的两倍就会使用期望容量;
如果当前切片的长度小于 1024 就会将容量翻倍;
如果当前切片的长度大于 1024 就会每次增加 25% 的容量,直到新容量大于期望容量;
1.5切片的复制
1.5.1赋值运算符
sliceA:=sliceB
赋值运算符的复制,只是复制切片本身的结构(指向底层数组的指针,长度,容量),修改sliceA,同样会修改底层数组。
1.512copy
copyNum:=copy(destSlice,srcSlice)
copy复制的是底层的数组,修改destSlice,不会影响srcSlice的底层数组,互不影响
二、切片的优化
2.1空数组,nil切片和空切片
空数组和空切片在内存中因为没有长度,指针都指向一个内存地址,可以用于强调某种特有类型。
特殊变量:zerobase
空结构体是没有内存大小的结构体。这句话是没有错的,但是更准确的来说,其实是有一个特殊起点的,那就是 zerobase
变量,这是一个 uintptr
全局变量,占用 8 个字节。当在任何地方定义无数个 struct {}
类型的变量,编译器都只是把这个 zerobase
变量的地址给出去。换句话说,在 golang 里面,涉及到所有内存 size 为 0 的内存分配,那么就是用的同一个地址 &zerobase
。
nil切片不占用空间,切片的零值是nil。
nil切片底层数据是空,并没有指向具体地址,用来表示一个不存在的切片。所以不会分配内存,不占用空间。
2.2切片扩容优化
1.根据切片扩容规则,减少不必要的扩容。
2.根据实际需要预分配容量,尽量避免在追加过程中的扩容操作
2.3sync.Pool
临时对象池,增加临时对象的重用率,减少内存分配,减少GC负担。
2.4切片复用
fasthttp中使用很多切片复用
三、切片内存泄漏
3.1避免错误使用切片导致内存泄漏
返回的[]byte指向保存整个文件的数组,由于垃圾回收器不能及时释放底层数组的空间
将数组赋值到新的切片
四、其它语言中与golang切片概念类似的结构
4.1.python切片
相同:语法基本相同(表达式语法基本相同,如下图),概念相似,都是指原数据的一部分。
不同:
1.底层结构不同。python切片的数据都是原数组的一个副本(是值)。不会对原数组有影响。而golang切片返回的只是一个由指向底层数组的指针,切片长度,切片容量组成的结构体,本身没有值。对切片进行操作会影响底层数组。
2.golang切片的API较少,比如想删除一个元素较复杂,且在数组头部添加会增加内存消耗。
4.2.c++的vector容器
扩容策略相似,大小,容量概念也相似。
参考文章: