上卷 程序设计
中卷 标准库
- bufio 1.18
- bytes 1.18
- io 1.18
- container 1.18
- encoding 1.18
- crypto 1.18
- hash 1.18
- index 1.18
- sort 1.18
- context 1.18
- database 1.18
- connection
- query
- queryrow
- exec
- prepare
- transaction
- scan & null
- context
- tcp
- udp
- http
- server
- handler
- client
- h2、tls
- url
- rpc
- exec
- signal
- embed 1.18
- plugin 1.18
- reflect 1.18
- runtime 1.18
- KeepAlived
- ReadMemStats
- SetFinalizer
- Stack
- sync 1.18
- atomic
- mutex
- rwmutex
- waitgroup
- cond
- once
- map
- pool
- copycheck
- nocopy
- unsafe 1.18
- fmt 1.18
- log 1.18
- math 1.18
- time 1.18
- timer
下卷 运行时
源码剖析
附录
文章来源于网络收集而来,版权归原创者所有,如有侵权请及时联系!
5.3 切片
切片以指针引用底层数组片段,限定读写区域。类似胖指针,而非动态数组或数组指针。
+---------+ +---+---+----//---+----+ | array -|----------> | 0 | 1 | ... ... | 99 | +---------+ +---+---+----//---+----+ | len | +---------+ array | cap | +---------+ header
// runtime/slice.go type slice struct { array unsafe.Pointer len int cap int }
- 引用类型,未初始化为
nil
。 - 基于数组、初始化值或
make
函数创建。
- 函数
len
返回元素数量,cap
返回容量。 - 仅能判断是否为
nil
,不支持其他==
、!=
操作。
- 以索引访问元素,可获取底层数组元素指针。
- 底层数组可能在堆上分配。
有关切片内部实现,请阅读《下卷:8.3 切片》。
+---+---+---+---+---+---+---+---+---+---+ | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | array +---+---+---+---+---+---+---+---+---+---+ | | | slice: [low : high : max] |<--- s.len --->| | | | len = high - low |<------- s.cap ------->| cap = max - low
func main() { a := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} // array s := a[2:6:8] fmt.Println(s) fmt.Println(len(s), cap(s)) // 引用原数组。 fmt.Printf("a: %p ~ %p\n", &a[0], &a[len(a) - 1]) fmt.Printf("s: %p ~ %p\n", &s[0], &s[len(s) - 1]) } /* s: [2 3 4 5], len = 4, cap = 6 a: 0xc00007a000 ~ 0xc00007a048 s: 0xc00007a010 ~ 0xc00007a028 */
slice.len
:限定索引或迭代读取数据范围。slice.cap
:重新切片(reslice)允许范围。
func main() { a := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} s := a[2:6:8] // fmt.Println(s[5]) // ~~~~ index out of range [5] with length 4 for i, x := range s { fmt.Printf("s[%d]: %d\n", i, x) } } /* # 切片引用原数组,此图只为方便理解。 +---+---+---+---+---+---+---+---+---+---+ | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | a: [10]int +---+---+---+---+---+---+---+---+---+---+ . . +---+---+---+---+---+---+ | 2 | 3 | 4 | 5 | | | s: a[2:6:8] +---+---+---+---+---+---+ 0 1 2 3 s[0]: 2 s[1]: 3 s[2]: 4 s[3]: 5 */
func main() { a := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} s := a[2:6:8] s1 := s[0:2:4] fmt.Println(s1, len(s1), cap(s1)) s2 := s[1:6] fmt.Println(s2, len(s2), cap(s2)) // _ = s[1:7] // ~~~~~~ slice bounds out of range [:7] with capacity 6 } /* # 切片的数据来自底层数组,重切片只是调整引用范围。 # 重切片受原 cap 限制,而非 len。 +---+---+---+---+---+ | 3 | 4 | 5 | 6 | 7 | s2: s[1:6] +---+---+---+---+---+ . . +---+---+---+---+---+---+ | 2 | 3 | 4 | 5 | | | s +---+---+---+---+---+---+ . . +---+---+---+---+ | 2 | 3 | | | s1: s[0:2:4] +---+---+---+---+ s1: [2 3], len = 2, cap = 4 s2: [3 4 5 6 7], len = 5, cap = 5 */
构造切片的常见方法。
func main() { a := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} s := a[:] fmt.Println(s, len(s), cap(s)) } /* expr slice len cap ----------+-----------------------+----+-----+--------------- a[:] [0 1 2 3 4 5 6 7 8 9] 10 10 a[0:len(a)] a[2:5] [2 3 4] 3 8 a[2:5:7] [2 3 4] 3 5 a[4:] [4 5 6 7 8 9] 6 6 a[4:len(a)] a[:4] [0 1 2 3] 4 10 a[0:4] a[:4:6] [0 1 2 3] 4 6 a[0:4:6] */
func main() { // 按初始化值,自动分配底层数组。 s1 := []int{ 0, 1, 2, 3 } fmt.Println(s1, len(s1), cap(s1)) // 自动创建底层数组。 s2 := make([]int, 5) fmt.Println(s2, len(s2), cap(s2)) s3 := make([]int, 0, 5) fmt.Println(s3, len(s3), cap(s3)) } /* s1: [0 1 2 3], len = 4, cap = 4 s2: [0 0 0 0 0], len = 5, cap = 5 s3: [], len = 0, cap = 5 */
作为引用类型,初始化与否很重要。
编译器可能将零长度对象(
len == 0 && cap == 0
)指向固定全局变量zerobase
。
func p(s []int) { fmt.Printf("%t, %d, %#v\n", s == nil, unsafe.Sizeof(s), (*reflect.SliceHeader)(unsafe.Pointer(&s))) } func main() { // 仅分配 header 内存,未初始化。 var s1 []int // 初始化。 s2 := []int{} // 调用 makeslice 初始化。 s3 := make([]int, 0) p(s1) p(s2) p(s3) } /* s1: true, 24, &SliceHeader{Data:0x0, Len:0, Cap:0} s2: false, 24, &SliceHeader{Data:0x5521d0, Len:0, Cap:0} s3: false, 24, &SliceHeader{Data:0x5521d0, Len:0, Cap:0} $ nm test | grep 5521d0 00000000005521d0 B runtime.zerobase */
不支持比较操作。
func main() { s1 := []int{ 1, 2 } s2 := []int{ 1, 2, 3 } println(s1 == nil) // println(s1 == s2) // ~~~~~~~~ invalid: slice can only be compared to nil }
函数 clear
仅将 [0, len)
范围内的元素重置为零值(memclr),不修改相关属性。
func main() { a := [...]int{ 1, 2, 10:100 } s := a[:2:6] fmt.Printf("%v, len:%d, cap:%d, ptr: %p\n", s, len(s), cap(s), &s[0]) clear(s) fmt.Printf("%v, len:%d, cap:%d, ptr: %p\n", s, len(s), cap(s), &s[0]) } /* [1 2], len:2, cap:6, ptr: 0xc000062060 [0 0], len:2, cap:6, ptr: 0xc000062060 */
转换
切片可直接转回数组或数组指针。
func main() { var a [4]int = [...]int{ 0, 1, 2, 3 } // array -> slice: 指向原数组 var s []int = a[:] println(&s[0] == &a[0]) // true // slice -> array: 复制底层数组(片段) a2 := [4]int(s) println(&a2[0] == &a[0]) // false // slice -> *array: 返回底层数组(片段)指针 p2 := (*[4]int)(s) println(p2 == &a) // true }
指针
可获取元素指针,但不能以切片指针访问元素。
func main() { a := [...]int{ 0, 1, 2, 3 } s := a[:] p := &s // 切片指针 e := &s[1] // 元素指针 // 数组指针直接指向元素所在内存。 // 切片指针指向 header 内存。 // _ = p[1] // ~~~~ invalid: cannot index p (variable of type *[]int) _ = (*p)[1] // 元素指针指向数组。 *e += 100 fmt.Println(e == &a[1]) // true fmt.Println(a) // [0 101 2 3] }
指针相关操作。
func main() { var a [4]int = [...]int{ 0, 1, 2, 3 } // 基于数组指针创建切片。 var p *[4]int = &a var s []int = p[:] println(&s[2] == &a[2]) // true // 基于非数组指针创建切片。 p2 := (*byte)(unsafe.Pointer(&a[2])) // 元素指针 var s2 []byte = unsafe.Slice(p2, 8) fmt.Println(s2) } // [2 0 0 0 0 0 0 0]
func main() { var a [3]byte = [...]byte{ 'a', 'b', 'c' } var s []byte = a[:] // 返回切片底层数组首个元素指针。 println(unsafe.SliceData(s) == &a[0]) // true // 构建字符串,返回底层数组指针。 var str string = unsafe.String(&s[0], len(s)) println(unsafe.StringData(str) == &a[0]) // true }
切片本身只是 3 个整数字段的小对象,可直接值传递。
另外,编译器尽可能将底层数组分配在栈上,以提升性能。
package main //go:noinline func sum(s []int) (n int) { for _, v := range s { n += v } return } func main() { s := []int{ 1, 2, 3 } println(sum(s)) } /* $ go build -o test $ go tool objdump -S -s "main\.main" ./test TEXT main.main(SB) func main() { s := []int{ 1, 2, 3 } 0x462c20 MOVQ $0x1, 0x20(SP) ; array 0x462c29 MOVQ $0x2, 0x28(SP) 0x462c32 MOVQ $0x3, 0x30(SP) println(sum(s)) ; header { 0x462c3b LEAQ 0x20(SP), AX ; .array = AX 0x462c40 MOVL $0x3, BX ; .len = BX 0x462c45 MOVQ BX, CX ; .cap = CX 0x462c48 CALL main.sum(SB) ; } 0x462c4d MOVQ AX, 0x18(SP) ; sum.return 0x462c52 CALL runtime.printlock(SB) 0x462c57 MOVQ 0x18(SP), AX 0x462c60 CALL runtime.printint(SB) 0x462c65 CALL runtime.printnl(SB) 0x462c6a CALL runtime.printunlock(SB) } */
交错
如果元素也是切片,可实现类似 交错数组 (jagged array)功能。
不同于多维数组元素等长,交错数组的元素可以是长度不等的数组。
func main() { s := [][]int{ {1, 2}, {10, 20, 30}, {100}, } s[1][2] += 100 fmt.Println(s[1]) } // [10 20 130]
追加
内置函数 append
向切片追加数据。
- 数据追加到
s[len]
处。 - 返回新切片对象,通常复用原内存。
s = append(s, ...)
- 超出
s.cap
限制,即便底层数组尚有空间,也会重新分配内存,并复制数据。 - 新分配内存大小,通常是
s.cap * 2
。更大切片,会减少倍数,避免浪费。
package main import ( "fmt" "reflect" "unsafe" ) func pslice(name string, p *[]int) { fmt.Printf("%s: %#v\n", name, *(*reflect.SliceHeader)(unsafe.Pointer(p))) } func main() { a := [...]int{ 0, 1, 2, 3, 99:0} fmt.Printf("a: %p ~ %p\n", &a[0], &a[len(a) - 1]) s := a[:4:8] pslice("s", &s) // ----------------------------- // 未超出 s.cap 限制。 s = append(s, []int{4, 5, 6}...) pslice("a", &s) // 超出! 新分配数组,复制数据。 s = append(s, []int{7, 8}...) pslice("a", &s) // ----------------------------- fmt.Println("a:", a[:len(s)]) fmt.Println("s:", s) } /* a: 0xc00007a000 ~ 0xc00007a318 s: {Data:0xc00007a000, Len:4, Cap:8} a: {Data:0xc00007a000, Len:7, Cap:8} a: {Data:0xc00001e080, Len:9, Cap:16} a: [0 1 2 3 4 5 6 0 0] s: [0 1 2 3 4 5 6 7 8] */
为切片预留足够容量(cap),可有效减少内存分配和复制。
s := make([]int, 0, 10000)
拷贝
在两个切片间复制数据:
- 允许指向同一数组。
- 允许目标区间重叠。
- 复制长度以较短(
len
)切片为准。
func main() { s := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} // 在同一底层数组的不同区间复制。 src := s[5:8] dst := s[4:] n := copy(dst, src) fmt.Println(n, s) // 在不同数组间复制。 dst = make([]int, 6) n = copy(dst, src) fmt.Println(n, dst) } /* 3 [0 1 2 3 5 6 7 7 8 9] 3 [6 7 7 0 0 0] */
还可直接从字符串中复制数据到字节切片。
func main() { b := make([]byte, 3) n := copy(b, "abcde") fmt.Println(n, b) } // 3 [97 98 99]
若切片引用大数组,那么应考虑新建并复制。及时释放大数组,避免内存浪费。
package main import ( "runtime" "time" ) func main() { d := [...]byte{ 100<<20: 10 } // -- 1 -------------- s := d[:2] // -- 2 -------------- // s := make([]byte, 2) // copy(s, d[:]) for i := 0; i < 5; i++ { time.Sleep(time.Second) runtime.GC() } runtime.KeepAlive(&s) } /* $ go build && GODEBUG=gctrace=1 ./test -- 1 ------------ gc 5 @6.188s 0%: ..., 100->100->100 MB, 200 MB goal, ..., 2 P (forced) -- 2 ------------ gc 5 @5.827s 0%: ..., 0->0->0 MB, 4 MB goal, ..., 2 P (forced) */
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论