slice
slice介紹
slice中文叫切片,是go官方提供的一個可變數組,是一個輕量級的數據結構,功能上和c++的vector,Java的ArrayList差不多。
slice和數組是有一些區別的,是為了彌補數組的一些不足而誕生的數據結構。最大的區別就是數組長度固定,不可擴容,而切片是可以擴容的。也就是這個功能的區別導致了后續一系列的區別,例如在傳參上面,數組是整個數組的值復制過去傳到函數里,而切片則是傳遞指針等。
?func change(arr *[3]int) {arr[0] = 1}func change1(arr [3]int) {arr[0] = 1}func TestName(t *testing.T) {arr := [3]int{1, 2, 3}arr[0] = 2change(&arr)//change1(arr) // 這兩者的結果是不一樣的fmt.Println(arr[0])}
那么slice是什么呢? slice結構體源碼如下:(在runtime/slice.go中)
?type slice struct {array unsafe.Pointer // 指向底層數組的指針len ? int ? ? ? ? ? ?// 當前長度cap ? int ? ? ? ? ? ?// 總容量}
slice擴容機制
1.18之前的方式和現在不太一樣,Go1.18之前切片的擴容是以容量1024為臨界點,當舊容量 < 1024個元素,擴容變成2倍;當舊容量 > 1024個元素,那么會進入一個循環,每次增加25%直到大于期望容量。
?func TestSliceGrowing(t *testing.T) {s := []int{}for i := 0; i < 4098; i++ {s = append(s, i)t.Log(len(s), cap(s))}}?作者:starine鏈接:https://juejin.cn/post/7101928883280150558來源:稀土掘金著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。
Go1.18不再以1024為臨界點,而是設定了一個值為256的threshold
,以256為臨界點;超過256,不再是每次擴容1/4,而是每次增加(舊容量+3*256)/4;
當新切片需要的容量大于兩倍的舊容量時,則直接按照新切片需要的容量擴容; else: 當原 slice 容量 < threshold 的時候,新 slice 容量變成原來的 2 倍; 當原 slice 容量 > threshold,進入一個循環,每次容量增加(舊容量+3*threshold)/4。
作者:starine 鏈接:https://juejin.cn/post/7101928883280150558 來源:稀土掘金 著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。
?func growslice(et *_type, old slice, cap int) slice {// ......newcap := old.capdoublecap := newcap + newcap ? ?//雙倍擴容(原容量的兩倍)if cap > doublecap { ? //如果所需容量大于 兩倍擴容,則直接擴容到所需容量newcap = cap} else {const threshold = 256 ? //這里設置了一個 閾值 -- 256if old.cap < threshold { //如果舊容量 小于 256,則兩倍擴容newcap = doublecap ? } else {// 檢查 0 < newcap 以檢測溢出并防止無限循環。for 0 < newcap && newcap < cap { ? //如果新容量 > 0 并且 原容量 小于 所需容量// 從小片的增長2x過渡到大片的增長1.25x。這個公式給出了兩者之間的平滑過渡。(這里的系數會隨著容量的大小發生變化,從2.0到無線接近1.25)newcap += (newcap + 3*threshold) / 4//當newcap計算溢出時,將newcap設置為請求的上限。if newcap <= 0 { ? // 如果發生了溢出,將新容量設置為請求的容量大小newcap = cap}}}}
具體情況如下:
如果請求容量 大于 兩倍現有容量 ,則新容量 直接為請求容量
?否則(請求容量 小于等于 兩倍現有容量) 如果 現有容量 小于 256 ,則新容量是原來的兩倍
?否則:新容量 = 1.25 原容量 + 3/4 閾值
golang slice (切片) 擴容機制詳解(1.18版本后) - 小星code - 博客園
這么設計的目的是為了擴容能平滑,更好地節省內存。
傳參問題
slice被make出來就是一個結構體的實例。當作為一個參數傳遞到方法里,會傳遞一個這個slice實例的值過去,這也是導致slice傳參會有一些列奇怪現象的原因(可以修改值但無法擴容等)。可以修改數值的原因是這個值中的array指針是指向原數組的,但是無法擴容的原因是修改這個值的指針對原slice是無影響的;而傳遞slice指針可以修改成功是因為本質是就是在修改原方法層面的slice,而不是修改傳遞后slice的值。
線程安全性問題
slice是線程不安全的數據結構,因此會存在競態條件(race condition),處理原則要么只讀,要么加鎖。
Slice 的底層是 數組指針 + len + cap,這幾個在并發時候都可能出現競態條件。