特别提醒,本文所涉及的源码是go1.21.0 darwin/amd64
文件位置:runtime/slice.go

1 数据结构

1
2
3
4
5
type slice struct {
array unsafe.Pointer
len int
cap int
}
  • array:指向存储元素内存空间的起始地点
  • len:切片的长度
  • cap:切片的容量

2 初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
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)
}

3 扩容

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
func growslice(oldPtr unsafe.Pointer, newLen, oldCap, num int, et *_type) slice {
// 旧切片的长度
oldLen := newLen - num
if newLen < 0 {
panic(errorString("growslice: len out of range"))
}
// 元素大小为0,则直接分配
if et.Size_ == 0 {
// append should not create a slice with nil pointer but non-zero len.
// We assume that append doesn't need to preserve oldPtr in this case.
return slice{unsafe.Pointer(&zerobase), newLen, newLen}
}
// 判断新切片的容量大小
newcap := oldCap
doublecap := newcap + newcap
// 如果新切片的长度大于旧切片容量的两倍,则将新切片的长度赋值给容量
if newLen > doublecap {
newcap = newLen
} else {
const threshold = 256
// 旧切片的容量小于256
if oldCap < threshold {
// 两倍扩容
newcap = doublecap
} else {
// 一直增加,直到大于切片的长度
for 0 < newcap && newcap < newLen {
// 扩容速度为1/4的原来容量+固定值192
newcap += (newcap + 3*threshold) / 4
}
// 超过int32类型大小,则将长度赋值给容量
if newcap <= 0 {
newcap = newLen
}
}
}

var overflow bool
var lenmem, newlenmem, capmem uintptr
switch {
case et.Size_ == 1: // 元素大小为1
lenmem = uintptr(oldLen)
newlenmem = uintptr(newLen)
capmem = roundupsize(uintptr(newcap))
overflow = uintptr(newcap) > maxAlloc
newcap = int(capmem)
case et.Size_ == goarch.PtrSize: // 元素是指针类型
lenmem = uintptr(oldLen) * goarch.PtrSize
newlenmem = uintptr(newLen) * goarch.PtrSize
capmem = roundupsize(uintptr(newcap) * goarch.PtrSize)
overflow = uintptr(newcap) > maxAlloc/goarch.PtrSize
newcap = int(capmem / goarch.PtrSize)
case isPowerOfTwo(et.Size_): // 元素大小是2的幂
var shift uintptr
if goarch.PtrSize == 8 {
shift = uintptr(sys.TrailingZeros64(uint64(et.Size_))) & 63
} else {
shift = uintptr(sys.TrailingZeros32(uint32(et.Size_))) & 31
}// 通过移位操作,性能更高效
lenmem = uintptr(oldLen) << shift
newlenmem = uintptr(newLen) << shift
capmem = roundupsize(uintptr(newcap) << shift)
overflow = uintptr(newcap) > (maxAlloc >> shift)
newcap = int(capmem >> shift)
capmem = uintptr(newcap) << shift
default:
lenmem = uintptr(oldLen) * et.Size_
newlenmem = uintptr(newLen) * et.Size_
capmem, overflow = math.MulUintptr(et.Size_, uintptr(newcap))
capmem = roundupsize(capmem)
newcap = int(capmem / et.Size_)
capmem = uintptr(newcap) * et.Size_
}

if overflow || capmem > maxAlloc {
panic(errorString("growslice: len out of range"))
}
// 新的内存地址
var p unsafe.Pointer
if et.PtrBytes == 0 { // 无指针
p = mallocgc(capmem, nil, false)
memclrNoHeapPointers(add(p, newlenmem), capmem-newlenmem)
} else {
// 有指针
p = mallocgc(capmem, et, true)
if lenmem > 0 && writeBarrier.enabled {
bulkBarrierPreWriteSrcOnly(uintptr(p), uintptr(oldPtr), lenmem-et.Size_+et.PtrBytes)
}
}
memmove(p, oldPtr, lenmem)

return slice{p, newLen, newcap}
}

扩容规则:

  1. 如果新切片的长度大于旧切片容量的两倍,则直接将新切片的长度作为容量
  2. 容量小于256,直接两倍扩容
  3. 大于256,则循环扩容加上1/4容量和一个固定值192,

4 切片拷贝

4.1 赋值拷贝

1
2
s1 := []int{1,2,3,4,5}
s2 := s1[1:]

这种通过赋值来拷贝的,两者会共用一个底层数组,意思就是两者指向的元素是相同的,即

image-20230901214335034

4.2 copy或者slicecopy

如果不想要两个指向的同一个数组,则通过copy或者slicecopy来进行拷贝,两种拷贝方式都会通过 runtime.memmove将整块内存的内容拷贝到目标的内存区域中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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 width == 0 {
return n
}
if size == 1 { // common case worth about 2x to do here
*(*byte)(toPtr) = *(*byte)(fromPtr) // known to be a byte pointer
} else {
memmove(toPtr, fromPtr, size)
}
return n
}

image-20230901214757321

5 追加和删除

追加操作比较简单通过append()即可实现

1
2
s := []int{2,3,4}  
s = append(s,5) // [2,3,4,5]

删除操作有如下几种情况

1
2
3
4
s := []int{0,1,2,3,4}
s = s[1:] // [1,2,3,4]

s = append(s[:2],s[3:]...) // [0,1,3,4]