由於數組大小是固定的,當數據元素特別多時,固定的數組沒法儲存這麼多的值,因此可變長數組出現了,這也是一種數據結構。在Golang
語言中,可變長數組被內置在語言裏面:切片slice
。算法
slice
是對底層數組的抽象和控制。它是一個結構體:segmentfault
type slice struct { array unsafe.Pointer len int cap int }
Golang
語言是沒有操做原始內存的指針的,因此unsafe
包提供相關的對內存指針的操做,通常狀況下非專業人員勿用)每次能夠初始化一個固定容量的切片,切片內部維護一個固定大小的數組。當append
新元素時,固定大小的數組不夠時會自動擴容,如:數組
package main import "fmt" func main() { // 建立一個容量爲2的切片 array := make([]int, 0, 2) fmt.Println("cap", cap(array), "len", len(array), "array:", array) // 雖然 append 可是沒有賦予原來的變量 array _ = append(array, 1) fmt.Println("cap", cap(array), "len", len(array), "array:", array) _ = append(array, 1) fmt.Println("cap", cap(array), "len", len(array), "array:", array) _ = append(array, 1) fmt.Println("cap", cap(array), "len", len(array), "array:", array) fmt.Println("-------") // 賦予回原來的變量 array = append(array, 1) fmt.Println("cap", cap(array), "len", len(array), "array:", array) array = append(array, 1) fmt.Println("cap", cap(array), "len", len(array), "array:", array) array = append(array, 1) fmt.Println("cap", cap(array), "len", len(array), "array:", array) array = append(array, 1, 1, 1, 1) fmt.Println("cap", cap(array), "len", len(array), "array:", array) array = append(array, 1, 1, 1, 1, 1, 1, 1, 1, 1) fmt.Println("cap", cap(array), "len", len(array), "array:", array) }
輸出:安全
cap 2 len 0 array: [] cap 2 len 0 array: [] cap 2 len 0 array: [] cap 2 len 0 array: [] ------- cap 2 len 1 array: [1] cap 2 len 2 array: [1 1] cap 4 len 3 array: [1 1 1] cap 8 len 7 array: [1 1 1 1 1 1 1] cap 16 len 16 array: [1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1]
咱們能夠看到Golang
的切片沒法原地append
,每次添加元素時返回新的引用地址,必須把該引用從新賦予以前的切片變量。而且,當容量不夠時,會自動倍數遞增擴容。事實上,Golang
在切片長度大於1024
後,會以接近於1.25
倍進行容量擴容。數據結構
具體可參考標準庫runtime
下的slice.go
文件。併發
咱們來實現一個簡單的,存放整數的,可變長的數組版本。app
由於Golang
的限制,不容許使用[n]int
來建立一個固定大小爲n
的整數數組,只容許使用常量來建立大小。數據結構和算法
因此咱們這裏會使用切片的部分功能來代替數組,雖然切片自己是可變長數組,可是咱們不會用到它的append
功能,只把它當數組用。函數
import ( "sync" ) // 可變長數組 type Array struct { array []int // 固定大小的數組,用滿容量和滿大小的切片來代替 len int // 真正長度 cap int // 容量 lock sync.Mutex // 爲了併發安全使用的鎖 }
建立一個len
個元素,容量爲cap
的可變長數組:指針
// 新建一個可變長數組 func Make(len, cap int) *Array { s := new(Array) if len > cap { panic("len large than cap") } // 把切片當數組用 array := make([]int, cap, cap) // 元數據 s.array = array s.cap = cap s.len = 0 return s }
主要利用滿容量和滿大小的切片來充當固定數組,結構體Array
裏面的字段len
和cap
來控制值的存取。不容許設置len > cap
的可變長數組。
時間複雜度爲:O(1)
,由於分配內存空間和設置幾個值是常數時間。
// 增長一個元素 func (a *Array) Append(element int) { // 併發鎖 a.lock.Lock() defer a.lock.Unlock() // 大小等於容量,表示沒多餘位置了 if a.len == a.cap { // 沒容量,數組要擴容,擴容到兩倍 newCap := 2 * a.len // 若是以前的容量爲0,那麼新容量爲1 if a.cap == 0 { newCap = 1 } newArray := make([]int, newCap, newCap) // 把老數組的數據移動到新數組 for k, v := range a.array { newArray[k] = v } // 替換數組 a.array = newArray a.cap = newCap } // 把元素放在數組裏 a.array[a.len] = element // 真實長度+1 a.len = a.len + 1 }
首先添加一個元素到可變長數組裏,會加鎖,這樣會保證併發安全。而後將值放在數組裏:a.array[a.len] = element
,而後len + 1
,表示真實大小又多了一個。
當真實大小len = cap
時,代表位置都用完了,沒有多餘的空間放新值,那麼會建立一個固定大小2*len
的新數組來替換老數組:a.array = newArray
,固然容量也會變大:a.cap = newCap
。若是一開始設置的容量cap = 0
,那麼新的容量會是從 1 開始。
添加元素中,耗時主要在老數組中的數據移動到新數組,時間複雜度爲:O(n)
。固然,若是容量夠的狀況下,時間複雜度會變爲:O(1)
。
如何添加多個元素:
// 增長多個元素 func (a *Array) AppendMany(element ...int) { for _, v := range element { a.Append(v) } }
只是簡單遍歷一下,調用Append
函數。其中...int
是Golang
的語言特徵,表示多個函數變量。
// 獲取某個下標的元素 func (a *Array) Get(index int) int { // 越界了 if a.len == 0 || index >= a.len { panic("index over len") } return a.array[index] }
當可變長數組的真實大小爲0,或者下標index
超出了真實長度len
,將會panic
越界。
由於只獲取下標的值,因此時間複雜度爲O(1)
。
// 返回真實長度 func (a *Array) Len() int { return a.len } // 返回容量 func (a *Array) Cap() int { return a.cap }
時間複雜度爲O(1)
。
如今咱們來運行完整的可變長數組的例子:
package main import ( "fmt" "sync" ) // 可變長數組 type Array struct { array []int // 固定大小的數組,用滿容量和滿大小的切片來代替 len int // 真正長度 cap int // 容量 lock sync.Mutex // 爲了併發安全使用的鎖 } // 新建一個可變長數組 func Make(len, cap int) *Array { s := new(Array) if len > cap { panic("len large than cap") } // 把切片當數組用 array := make([]int, cap, cap) // 元數據 s.array = array s.cap = cap s.len = 0 return s } // 增長一個元素 func (a *Array) Append(element int) { // 併發鎖 a.lock.Lock() defer a.lock.Unlock() // 大小等於容量,表示沒多餘位置了 if a.len == a.cap { // 沒容量,數組要擴容,擴容到兩倍 newCap := 2 * a.len // 若是以前的容量爲0,那麼新容量爲1 if a.cap == 0 { newCap = 1 } newArray := make([]int, newCap, newCap) // 把老數組的數據移動到新數組 for k, v := range a.array { newArray[k] = v } // 替換數組 a.array = newArray a.cap = newCap } // 把元素放在數組裏 a.array[a.len] = element // 真實長度+1 a.len = a.len + 1 } // 增長多個元素 func (a *Array) AppendMany(element ...int) { for _, v := range element { a.Append(v) } } // 獲取某個下標的元素 func (a *Array) Get(index int) int { // 越界了 if a.len == 0 || index >= a.len { panic("index over len") } return a.array[index] } // 返回真實長度 func (a *Array) Len() int { return a.len } // 返回容量 func (a *Array) Cap() int { return a.cap } // 輔助打印 func Print(array *Array) (result string) { result = "[" for i := 0; i < array.Len(); i++ { // 第一個元素 if i == 0 { result = fmt.Sprintf("%s%d", result, array.Get(i)) continue } result = fmt.Sprintf("%s %d", result, array.Get(i)) } result = result + "]" return } func main() { // 建立一個容量爲3的動態數組 a := Make(0, 3) fmt.Println("cap", a.Cap(), "len", a.Len(), "array:", Print(a)) // 增長一個元素 a.Append(10) fmt.Println("cap", a.Cap(), "len", a.Len(), "array:", Print(a)) // 增長一個元素 a.Append(9) fmt.Println("cap", a.Cap(), "len", a.Len(), "array:", Print(a)) // 增長多個元素 a.AppendMany(8, 7) fmt.Println("cap", a.Cap(), "len", a.Len(), "array:", Print(a)) }
將打印出:
cap 3 len 0 array: [] cap 3 len 1 array: [10] cap 3 len 2 array: [10 9] cap 6 len 4 array: [10 9 8 7]
能夠看到,容量會自動翻倍。
可變長數組在實際開發上,常常會使用到,其在固定大小數組的基礎上,會自動進行容量擴展。
由於這一數據結構的使用頻率過高了,因此,Golang
自動提供了這一數據類型:切片(可變長數組)。你們通常開發過程當中,直接使用這一類型便可。
我是陳星星,歡迎閱讀我親自寫的 數據結構和算法(Golang實現),文章首發於 閱讀更友好的GitBook。