摘要: 今天我们来学习 Golang 中的一个基本的数据结构 slice, 这个和 C++ 中的 vector 容器思想基本一致,是一个容量可变的数组,那我们就来看下它和 array 的区别和联系,并对其中的典型操作给出分析。
数据结构
// StringHeader is the runtime representation of a string. // It cannot be used safely or portably and its representation may // change in a later release. // Moreover, the Data field is not sufficient to guarantee the data // it references will not be garbage collected, so programs must keep // a separate, correctly typed pointer to the underlying data. type StringHeader struct { Data uintptr Len int } // stringHeader is a safe version of StringHeader used within this package. type stringHeader struct { Data unsafe.Pointer Len int } // SliceHeader is the runtime representation of a slice. // It cannot be used safely or portably and its representation may // change in a later release. // Moreover, the Data field is not sufficient to guarantee the data // it references will not be garbage collected, so programs must keep // a separate, correctly typed pointer to the underlying data. type SliceHeader struct { Data uintptr Len int Cap int } // sliceHeader is a safe version of SliceHeader used within this package. type sliceHeader struct { Data unsafe.Pointer Len int Cap int }
通过上面的结构体,我们可以把 slice 看成一片连续的内存空间加上长度与容量的标识。
slice 和 array
与其它语言一样, slice 是 长度可变的数组,我们其实可以把切片看做是对数组的一层简单封装,因为在每个切片的底层数据结构中,一定会包含一个数组。数组可以被叫做切片的底层数组,而切片也可以被看做是对数组的某个连续片段的引用。
slice 的扩容
这里我们用 append 来向切片追加元素,我们首先会先对切片结构体进行解构获取它的数组指针、大小和容量,如果在追加元素后切片的大小大于容量,那么就会调用 runtime.growslice 对切片进行扩容,扩容就是为切片分配一块新的内存空间并将原切片的元素全部拷贝过去,具体代码如下:
func growslice(et *_type, old slice, cap int) slice { newcap := old.cap doublecap := newcap + newcap if cap > doublecap { newcap = cap } else { if old.len < 1024 { newcap = doublecap } else { for 0 < newcap && newcap < cap { newcap += newcap / 4 } if newcap <= 0 { newcap = cap } } }
总结上述代码:1:如果期望容量大于当前容量的两倍就会使用期望容量;
2;如果当前切片的长度小于 1024 就会将容量翻倍;
3:如果当前的切片长度大于 1024 就会每次增加 25% 的容量,直到新容量大于期望容量;
参考资料:
https://draveness.me/golang/docs/part2-foundation/ch03-datastructure/golang-array-and-slice