Golang1.17源码分析之slice-001
Golang1.17 学习笔记001
一、slice
核心概念:切片和数组之间、切片和切片之间都是共享内存的。只要切片不发生扩容,那么都是操作的同一个地址
arr := [5]int{1,2,3,4,5}
s1 := arr[2:5]
arr[2] = 9
fmt.Println(s1) // [9,4,5] -- 共享内存
s1 = append(s1,6) // 发生扩容,不再共享内存
arr[3] = 10
fmt.Println(s1) // [9,4,5,6]
fmt.Println(arr) // [1,2,9,4,5]
源码包:runtime/slice.go
数据结构:
type slice struct {
array unsafe.Pointer // 指向底层数组的指针
len int // 长度
cap int // 容量
}
创建:这里创建规则跟 go 的内存管理有关,建议看完内存管理之后再看这里
-
小于 32kb 的对象从 per-p 缓存空闲列表中创建
-
大于 32kb 的对象从堆中进行创建
-
使用 new 创建 slice 无法直接使用 slice[0] 这种来赋值,需要使用 append 添加,此时才会给 slice 分配底层数组
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)
}
// Small objects are allocated from the per-P cache's free lists.
// Large objects (> 32 kB) are allocated straight from the heap.
func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
if size <= maxSmallSize { // maxSmallSize = _MaxSmallSize = 32768
size = uintptr(class_to_size[sizeclass])
spc := makeSpanClass(sizeclass, noscan)
span = c.alloc[spc]
v := nextFreeFast(span)
if v == 0 {
v, span, shouldhelpgc = c.nextFree(spc)
}
x = unsafe.Pointer(v)
} else {
span, isZeroed = c.allocLarge(size, needzero && !noscan, noscan)
span.freeindex = 1
span.allocCount = 1
x = unsafe.Pointer(span.base())
}
}
复制:
为什么 copy(小,大) 可以不报错,原因如下
func slicecopy(toPtr unsafe.Pointer, toLen int, fromPtr unsafe.Pointer, fromLen int, width uintptr) int {
if fromLen == 0 || toLen == 0 {
return 0
}
n := fromLen
if toLen < n {
n = toLen
}
if size == 1 { // common case worth about 2x to do here
// TODO: is this still worth it with new memmove impl?
*(*byte)(toPtr) = *(*byte)(fromPtr) // known to be a byte pointer
} else {
memmove(toPtr, fromPtr, size)
}
return n
}
扩容:
- 如果新容量大于 2 倍原容量,那么就扩容至新容量
- 如果新容量小于 2 倍原容量,且原容量小于 1024,那么新容量扩容至 2 倍原容量
- 如果新容量小于 2 倍原容量,且原容量大于 1024,那么新容量一直按原容量的 1/4 进行扩容,直到当前容量大于传入的新容量
func growslice(et *_type, old slice, cap int) slice {
newcap := old.cap
doublecap := newcap + newcap
if cap > doublecap {
newcap = cap
} else {
if old.cap < 1024 {
newcap = doublecap
} else {
// Check 0 < newcap to detect overflow
// and prevent an infinite loop.
for 0 < newcap && newcap < cap {
newcap += newcap / 4
}
// Set newcap to the requested cap when
// the newcap calculation overflowed.
if newcap <= 0 {
newcap = cap
}
}
}
memmove(p, old.array, lenmem)
return slice{p, old.len, newcap}
}
参考文献:《GO专家编程》之 slice 篇、Golang1.17源码