《GO语言圣经》学习笔记(四)切片

切片实现原理

切片,则是由一个指向数组的指针,切片的长度和容量两个int组成

切片的长度就是它所包含的元素个数。
切片的容量是从它的第一个元素开始数,到其底层数组元素末尾的个数。

切片内存分配

区分空切片和nil切片

nil切片

空切片

一個零值的slice等於nil。一個nil值的slice併沒有底層數組。一個nil值的slice的長度和容量都是0,但是也有非nil值的slice的長度和容量也是0的,例如[]int{}或make([]int, 3)[3:]。與任意類型的nil值一樣,我們可以用[]int(nil)類型轉換表達式來生成一個對應類型slice的nil值。

var s []int    // len(s) == 0, s == nil
s = nil        // len(s) == 0, s == nil
s = []int(nil) // len(s) == 0, s == nil
s = []int{}    // len(s) == 0, s != nil

如果你需要測試一個slice是否是空的,使用len(s) == 0來判斷,而不應該用s == nil來判斷。除了和nil相等比較外,一個nil值的slice的行爲和其它任意0産長度的slice一樣;例如reverse(nil)也是安全的。除了文檔已經明確説明的地方,所有的Go語言函數應該以相同的方式對待nil值的slice和0長度的slice。

內置的make函數創建一個指定元素類型、長度和容量的slice。容量部分可以省略,在這種情況下,容量將等於長度。

make([]T, len)
make([]T, len, cap) // same as make([]T, cap)[:len]

在底層,make創建了一個匿名的數組變量,然後返迴一個slice;隻有通過返迴的slice才能引用底層匿名的數組變量。在第一種語句中,slice是整個數組的view。在第二個語句中,slice隻引用了底層數組的前len個元素,但是容量將包含整個的數組。額外的元素是留給未來的增長用的。

切片的扩容原理

go中数组不可扩容,跳过不做讨论。slice扩容规则如下:

  • 如果切片的容量小于1024个元素,那么扩容的时候slice的cap就翻番,乘以2;一旦元素个数超过1024个元素,增长因子就变成1.25,即每次增加原来容量的四分之一。
  • 如果扩容之后,还没有触及原数组的容量,那么,切片中的指针指向的位置,就还是原数组,如果扩容之后,超过了原数组的容量,那么,Go就会开辟一块新的内存,把原来的值拷贝过来,这种情况丝毫不会影响到原数组。
    我们用一个例子来加深理解,请说出输出结果:
package main

import (
    "fmt"
)

func main() {
    arr := [4]int{10, 20, 30, 40}
    slice := arr[0:2]
    testSlice1 := slice
    testSlice2 := append(append(append(slice, 1), 2), 3)
    slice[0] = 11

    fmt.Println(testSlice1[0])
    fmt.Println(testSlice2[0])
}

根据第二条规则,append三个元素后,slice超出了原本的arr的长度,已经变成了一个新的切片,所以slice[0]=11不再能影响testSlice2,输出结果:

11
10

更新slice变量不仅对调用append函数是必要的,实际上对任何可能导致长度、容量或底层数组变化的操作都是必要的

引用


欢迎大家关注我的公众号


半亩房顶

推荐阅读更多精彩内容