什么是 Slice
切片是基于数组实现的,底层是数组,可以理解为对底层数组的抽象
数据结构
|
|
初始化
|
|
基于切片和数组创建的切片会和原数组或切片共享底层空间,修改切片会影响原数组或切片
扩容
|
|
- V1.18 版本之前
- 如果所需容量大于原容量的二倍,则新容量为所需容量。
- 如果原slice容量小于1024,则新slice容量将扩大为原来的二倍。
- 如果原slice容量大于或等于1024,则新slice容量将扩大为原来的1.25倍。
- V1.18 版本
- 如果所需容量大于原容量的二倍,则新容量为所需容量。
- 如果原slice容量小于256,则新slice容量将扩大为原来的二倍。
- 如果原容量大于或等于256,进入一个循环,每次容量增加(旧容量 + 3 * threshold)/ 4 。
而每次扩容后,切片将会开辟一块新的空间,将原切片中的数据拷贝到新地址中,而原先的那片空间就会被抛弃(仅限于没有其他指针指向原先的老空间)
Array 和 Slice 的区别
数组是固定长度,其大小在定义阶段就必须确定,函数传递中数组传递值。它的物理空间连续,性能相对较高,适合存储已知数量元素的场景。
切片是动态的序列,其是一个引用类型,指向了底层数组,大小可变,函数传递中传递引用。性能较低,但是灵活性高,适合数量未知或者频繁需要增删的场景
Slice 线程安全嘛
slice 底层结构并没有使用加锁等方式,不支持并发读写,所以并不是线程安全的,使用多个 goroutine 对类型为 slice 的变量进行操作,每次输出的值大概率都不会一样,与预期值不一致; slice在并发执行中不会报错,但是数据会丢失
Slice 深拷贝和浅拷贝
深拷贝
拷贝的是数据本身,创造一个新对象,新创建的对象与原对象不共享内存,新创建的对象在内存中开辟一个新的内存地址,新对象值修改时不会影响原对象值
|
|
浅拷贝
拷贝的是数据地址,只复制指向的对象的指针,此时新对象和老对象指向的内存地址是一样的,新对象值修改时老对象也会变化
|
|
nil slice 和 空slilce
nil slice 表示未初始化的切片,不分配任何内存,其值为 nil
空 slice 表示已初始化但长度为零,分配内存
二者并不相等,但是对这两个都可以进行 append 操作
1 2 3 4 5 6 7 8 9 10 11 12
func main() { var a []int // nil slice b := []int{} // empty slice fmt.Println(a == nil) // true fmt.Println(b == nil) // false fmt.Println(len(a), cap(a)) // 0 0 fmt.Println(len(b), cap(b)) // 0 0 a = append(a, 1) // a现在是包含一个元素的切片 [1] fmt.Println(a, len(a), cap(a)) // [1] 1 1 b = append(b, 1) // b现在也是包含一个元素的切片 [1] fmt.Println(a, len(a), cap(a)) // [1] 1 1 }