slice 切片详解
1、slice 实现原理
Slice又称动态数组,依托数组实现,可以方便的进行扩容、传递等,实际使用中比数组更灵活。 Slice 依托数组实现,底层数组对用户屏蔽,在底层数组容量不足时可以实现自动重分配并生成新的 Slice 。源码包中 src/runtime/slice.go:slice 定义了 Slice 的数据结构: type slice struct {
array unsafe.Pointer
len int
cap int
}
从数据结构看Slice很清晰, array指针指向底层数组,len表示切片长度,cap表示底层数组容量。
1.2 使用make创建Slice
使用 make 来创建 Slice 时,可以同时指定长度和容量,创建时底层会分配一个数组,数组的长度即容量。 例如,语句 slice := make([]int, 5, 10) 所创建的 Slice ,结构如下图所示:
该 Slice 长度为 5 ,即可以使用下标 slice[0] ~ slice[4] 来操作里面的元素, capacity 为 10 ,表示后续向 slice 添加新的元素时可以不必重新分配内存,直接使用预留内存即可。
1.3 使用数组创建Slice
使用数组来创建 Slice 时, Slice 将与原数组共用一部分内存。 例如,语句 slice := array[5:7] 所创建的 Slice ,结构如下图所示:
切片从数组 array[5] 开始,到数组 array[7] 结束(不含 array[7] ),即切片长度为 2 ,数组后面的内容都作为切片的预留内存, 即capacity 为 5 。 数组和切片操作可能作用于同一块内存,这也是使用过程中需要注意的地方。
2、Slice 扩容
2.1、slice 扩容影响原数组
append 函数会改变 slice 所引用的数组的内容,从而影响到引用同一数组的其它 slice。但当 slice 中没有剩余空间(即(cap-len) == 0)时,此时将动态分配新的数组空间。返回的slice 数组指针将指向这个空间,而原数组的内容将保持不变;其它引用此数组的 slice 则不受影响。
func testSlice() {var ar = [...]byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'}// 打印结果:abcdefghfmt.Printf("%s \n", ar)slice1 := ar[2:5]// 注:append 函数会改变 slice 所引用的数组的内容,从而影响到引用同一数组的其它 slice。// 但当 slice 中没有剩余空间(即(cap-len) == 0)时,此时将动态分配新的数组空间。返回的// slice 数组指针将指向这个空间,而原数组的内容将保持不变;其它引用此数组的 slice 则不受影响。slice1 = append(slice1, 'A')// 打印结果:cdeAfmt.Printf("%s \n", slice1)// 打印结果:abcdeAghfmt.Printf("%s \n", ar)
} 2.2、slice 扩容注意事项
使用 append 向 Slice 追加元素时,如果 Slice 空间不足,将会触发 Slice 扩容,扩容实际上是重新分配一块更大的内存,将原Slice数据拷贝进新 Slice ,然后返回新 Slice ,扩容后再将数据追加进去。 例如,当向一个 capacity 为 5 ,且 length 也为 5 的 Slice 再次追加 1 个元素时,就会发生扩容,如下图所示:
扩容操作只关心容量,会把原 Slice 数据拷贝到新 Slice ,追加数据由 append 在扩容结束后完成。上图可见,扩容后新的 Slice 长 度仍然是 5 ,但容量由 5 提升到了 10 ,原 Slice 的数据也都拷贝到了新 Slice 指向的数组中。 2.3、扩容容量的选择遵循以下规则:
1、如果原Slice 容量小于 1024 ,则新 Slice 容量将扩大为原来的 2 倍; 2、如果原Slice 容量大于等于 1024 ,则新 Slice 容量将扩大为原来的 1.25 倍;2.4、使用append()向Slice添加一个元素的实现步骤如下:
1、假如Slice 容量够用,则将新元素追加进去, Slice.len++ ,返回原 Slice 2、原Slice 容量不够,则将 Slice 先扩容,扩容后得到新 Slice 3、将新元素追加进新Slice , Slice.len++ ,返回新的 Slice 。
3、总结
1、创建切片时可根据实际需要预分配容量,尽量避免追加过程中扩容操作,有利于提升性能; 2、切片拷贝时需要判断实际拷贝的元素个数 3、谨慎使用多个切片操作同一个数组,以防读写冲突 4、每个切片都指向一个底层数组 5、 每个切片都保存了当前切片的长度、底层数组可用容量 6、 使用 len() 计算切片长度时间复杂度为 O(1) ,不需要遍历切片 7、使用cap() 计算切片容量时间复杂度为 O(1) ,不需要遍历切片 8、通过函数传递切片时,不会拷贝整个切片,因为切片本身只是个结构体而已 9、使用append() 向切片追加元素时有可能触发扩容,扩容后将会生成新的切片本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
