Golang的切片是一種非常常用的資料結構,它可以動态地調整長度并通路其元素。在本文中,我們将深入探讨Golang切片的源碼實作,以便更好地了解其内部工作原理。
首先,我們來看一下切片的定義方式:
var s []int // 聲明一個空切片 s := make([]int, 10) // 建立一個長度為10的切片
可以看到,切片的定義方式比較簡單,但是其内部實作卻非常複雜。在Golang中,切片是由三部分組成的:指向底層數組的指針、切片長度和切片容量。其中,切片長度表示切片目前所包含元素的個數,而切片容量則表示底層數組中可以存儲的元素個數。
接下來,我們來看一下Golang切片的底層資料結構:
type slice struct { array unsafe.Pointer // 指向底層數組的指針 len int // 切片長度 cap int // 切片容量 }
在這個結構體中,array字段是一個unsafe.Pointer類型的指針,它指向底層數組的第一個元素。len字段表示切片的長度,而cap字段則表示切片的容量。
Golang切片内部維護了一個指向底層數組的指針和兩個整型值。在底層數組空間不足的情況下,Golang會重新配置設定一塊更大的數組,然後将原有元素拷貝到新的數組中。這種動态擴容的實作方式為切片的高效使用提供了基礎保障。
下面,我們來看一下切片擴容的實作方式。在Golang中,切片擴容的政策是根據目前切片容量選擇一個新的容量,并且新的容量要比目前容量大一些。具體的實作方式如下:
newCap := cap(s) + cap(s)/2 newSlice := make([]T, len(s), newCap)copy(newSlice, s)
在這段代碼中,newCap表示新的容量值,它是目前容量值的1.5倍。然後,我們建立一個新的切片newSlice,其長度和原有切片長度相同,但是容量為newCap。最後,我們使用copy函數将原有切片中的元素複制到新的切片中。
需要注意的是,Golang切片的底層數組是一個連續的記憶體空間,是以切片的元素在記憶體中也是連續存儲的。這種記憶體布局為切片的高效通路提供了便利。當我們使用切片的append方法添加元素時,如果目前切片的長度已經等于其容量,Golang就會自動對其進行擴容。
下面,我們來看一下append方法的實作方式。在Golang中,append方法的實作方式是通過調用内置函數growslice來實作的。growslice函數的主要功能是計算新的切片容量,并且重新配置設定記憶體空間。具體的實作方式如下:
func growslice(et *_type, old slice, cap int) slice { newCap := old.capdoublecap := newCap + newCap if cap > doublecap { newCap = cap } else {if old.cap < 1024 { newCap = doublecap } else { for newCap < cap { newCap += newCap / 4 } } } newp := mallocgc(et.size * newCap) if et.ptrdata == 0{ memclr(newp, uintptr(et.size*newCap)) } else { for i := range old { typedmemmove(et, add(newp, uintptr(i)*et.size), add(old.array, uintptr(i)*et.size)) } } return slice{newp, old.len, newCap} }
在這段代碼中,et參數表示元素的類型,old參數表示原有的切片,cap參數表示新的容量值。首先,我們根據原有切片的容量計算出新的切片容量。如果新的容量值大于原有容量的兩倍,那麼新的容量值就是cap參數的值。否則,如果原有容量小于1024,新的容量值就是原有容量的兩倍;如果原有容量大于等于1024,就循環計算新的容量值,直到其大于等于cap參數的值。
然後,我們使用mallocgc函數為新的切片配置設定記憶體空間。如果元素類型的指針資訊為0,那麼我們就可以直接使用memclr函數清空新的切片記憶體空間。否則,我們就需要使用typedmemmove函數将原有切片中的元素拷貝到新的切片中。
最後,我們傳回一個新的切片,其指向新的記憶體空間、長度等于原有切片長度,容量等于新的容量值。
綜上所述,Golang切片的底層實作非常複雜,涉及到記憶體配置設定、擴容、拷貝等操作。然而,Golang切片的高效使用和動态擴容功能為我們提供了便利,使得我們在編寫代碼時可以更加靈活和高效。