Golang—Slice
Slice是Go语言中的一种数据类型,又称动态数组,依托数组实现,可以方便的进行扩容、传递等,实际使用中比数组更灵活。
实现原理
Slice依托数组实现,底层数组对用户屏蔽,在底层数组容量不足时可以实现自动重分配并生成新的Slice。
扩容
slice的扩容可以分为以下两种情况:
- 原slice的容量够用
这种情况下,扩容以后的指针还是指向原来的数组,对一个slice的操作可能影响多个指针指向相同地址的 Slice。
- 原slice的容量不够用
如果原来数组的容量已经达到了最大值,再想扩容, Go 默认会先开一片内存区 域,把原来的值拷贝过来,然后再执行 append() 操作。也就是说这是2块不同的内存地址,相互不影响。
(这里我只是做个示范,并不代表就一定会扩容2个cap,具体的扩容还需要按照扩容规则来)
具体扩容规则
规则1
- 需要的容量大于原切片容量的两倍时,会使用需要的容量作为新容量;
- 当原切片长度小于1024时,新切片的容量会直接翻倍;
- 而当原切片的容量大于等于1024时,会反复地增加25%,也就是变为原来容量的1.25倍,直到新容量超过所需要的容量。
规则2
- 如果扩容之后还没有超过原数组的容量,那切片中的指针指向的位置还是原数组;
- 如果扩容之后超过了原数组的容量,那就会开辟一块新的内存,把原来的值拷贝过来,这种情况丝毫不会影响到原数组。
使用append()向Slice添加一个元素的实现步骤如下:
- 假如Slice容量够用,则将新元素追加进去,Slice.len++,返回原Slice
- 原Slice容量不够,则将Slice先扩容,扩容后得到新Slice
- 将新元素追加进新Slice,Slice.len++,返回新的Slice。
总而言之,扩容只关心的slice的cap,长度是跟着append进行变化的。
深浅copy
- 深拷贝:拷贝的是数据本身,创造一个新对象,新创建的对象与原对象不共享内存,新创建的对象在内存中开辟一个新的内存地址,新对象值修改时不会影响原对象值
实现深拷贝的方式:
- copy(slice2, slice1)
- 并且使用copy()内置函数拷贝两个切片时,会将源切片的数据逐个拷贝到目的切片指向的数组中,拷贝数量取两个切片长度的最小值,多余的则会丢失。也就是说copy过程中不会发生扩容。
- 例如下图(slice2中的9,10则会丢失)
- 遍历append赋值
- 浅拷贝:拷贝的是数据地址,只复制指向的对象的指针,此时新对象和老对象指向的内存地址是一样的,新对象值修改时老对象也会变化
实现浅拷贝的方式:
引用类型的变量,默认赋值操作就是浅拷贝
slice2 := slice1
Slice总结
- 每个Slice底层都是由一个array来进行维护。
- slice的扩容需要根据实际情况进行实际判断。
- 使用append()向切片追加元素时有可能触发扩容,扩容后将会生成新的切片