Slice
1 2 3 4 5
| type SliceHeader struct { Data uintptr Len int Cap int }
|
初始化
1 2 3
| arr[0:3] or slice[0:3] slice := []int{1, 2, 3} slice := make([]int, 10)
|
1 2 3
| nums := make([]int,0) nums := []int{}
|
- empty和nil都可以使用append,底层会申请内存进行扩容
- 字面量创建
1 2 3 4 5 6 7
| var vstat [3]int vstat[0] = 1 vstat[1] = 2 vstat[2] = 3 var vauto *[3]int = new([3]int) *vauto = vstat slice := vauto[:]
|
[:] 就是使用下标创建切片的方法,从这一点也能看出 [:] 操作是创建切片最底层的一种方法。
如果使用字面量的方式创建切片,大部分的工作都会在编译期间完成。但是当我们使用 make
关键字创建切片时,很多工作都需要运行时的参与;调用方必须向 make
函数传入切片的大小以及可选的容量。
1 2 3
| 1. 检查len是否传入,检查len是否小于cap 2. 当切片发生逃逸或者非常大时,运行时需要 runtime.makeslice 在堆上初始化切片 3. 当前的切片不会发生逃逸并且切片非常小时,会使用与字面量相同的方式创建
|
使用 makeslice 创建切片
1 2 3 4 5 6 7 8 9 10 11 12
| func makeslice(et *_type, len, cap int) unsafe.Pointer { mem, overflow := math.MulUintptr(et.size, uintptr(cap)) if overflow || mem > maxAlloc || len < 0 || len > cap { mem, overflow := math.MulUintptr(et.size, uintptr(len)) if overflow || mem > maxAlloc || len < 0 { panicmakeslicelen() } panicmakeslicecap() }
return mallocgc(mem, et, true) }
|
计算切片所需空间并在堆上申请一段连续的内存 (内存空间=切片中元素大小×切片容量)
如果发生一下情况,会崩溃
- 内存空间的大小发生了溢出;
- 申请的内存大于最大可分配的内存;
- 传入的长度小于 0 或者长度大于容量;
mallocgc
是用于申请内存的函数,遇到了比较小的对象会直接初始化在 Go 语言调度器里面的 P 结构中,而大于 32KB 的对象会在堆上初始化。
追加和扩容
1 2 3 4 5
| a := make([]int,0,1)
a = append(a,1)
a = append(a,2)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| func growslice(et *_type, old slice, cap int) slice { ... newcap := old.cap doublecap := newcap + newcap if cap > doublecap { newcap = cap } else { if old.len < 1024 { newcap = doublecap } else { for 0 < newcap && newcap < cap { newcap += newcap / 4 } ... } } ... }
|
在分配内存空间之前需要先确定新的切片容量,运行时根据切片的当前容量选择不同的策略进行扩容:
- 如果期望容量大于当前容量的两倍就会使用期望容量;
- 如果当前切片的长度小于 1024 就会将容量翻倍;
- 如果当前切片的长度大于 1024 就会每次增加 25% 的容量,直到新容量大于期望容量;
上述代码片段仅会确定切片的大致容量,下面还需要根据切片中的元素大小对齐内存,当数组中元素所占的字节大小为 1、8 或者 2 的倍数时,运行时会使用如下所示的代码对齐内存:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| var overflow bool var lenmem, newlenmem, capmem uintptr switch { case et.size == 1: lenmem = uintptr(old.len) newlenmem = uintptr(cap) capmem = roundupsize(uintptr(newcap)) overflow = uintptr(newcap) > maxAlloc newcap = int(capmem) case et.size == sys.PtrSize: lenmem = uintptr(old.len) * sys.PtrSize newlenmem = uintptr(cap) * sys.PtrSize capmem = roundupsize(uintptr(newcap) * sys.PtrSize) overflow = uintptr(newcap) > maxAlloc/sys.PtrSize newcap = int(capmem / sys.PtrSize) case isPowerOfTwo(et.size): ... default: ... }
var class_to_size = [_NumSizeClasses]uint16{0,8,16,32,48,64,80,...}
|
例子
1 2 3 4 5 6 7 8 9
| a = []int{} a = a.append(1,2,3,4,5)
a = []int{1,2,3,4} a = a.append(5)
|
拷贝切片
传值和传指针
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| package main
import "fmt"
func myAppend(s []int) []int { s = append(s, 100) return s }
func myAppendPtr(s *[]int) { *s = append(*s, 100) return }
func main() { s := []int{1, 1, 1} newS := myAppend(s)
fmt.Println(s) fmt.Println(newS)
s = newS
myAppendPtr(&s) fmt.Println(s) }
|