由于数组大小是固定的,当数据元素特别多时,固定的数组没法储存这么多的值,因此可变长数组出现了,这也是一种数据结构。在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。