开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第36天,点击查看活动详情
前言
在上一篇文章中 Go常用数据结构的高性能实战—— string 篇(二) 我们学习了 strings.Join 原理以及 string 的可变性。在分析 strings.Join 原理时,主要是应用了切片和它的 append 操作追加字符串实现的字符串拼接。今天,我们就来唠一唠切片这个数据结构和它的高性能实战吧~
切片简介
切片是一种数据结构,这种数据结构便于使用和管理数据集合。切片是围绕动态数组的概念构建的,可以按需自动增长和缩小。切片的动态增长是通过内置函数 append 来实现的。这个函数可以快速且高效地增长切片。还可以通过对切片再次切片来缩小一个切片的大小。因为切片的底层内存也是在连续块中分配的,所以切片还能获得索引、迭代以及为垃圾回收优化的好处。
切片的内存布局
跟 string 一样,切片也有它独特的内存布局:
type slice struct {
array unsafe.Pointer
len int
cap int
}
- array 表示一个指向数组的指针
- len 表示切片的长度
- cap 表示切片的最大容量
从一个切片生成另一个切片
我们知道,切片其实是一块操作底层数组的数据结构。如果从一个切片生成另一个切片,那么这两个切片指向的底层数组其实是一样的。例如:
slice1 := []byte("zhongger")
slice2 := slice1[1 : len(slice1)-1]
fmt.Printf("slice1 : %s \n", slice1)
fmt.Printf("slice2 : %s \n", slice2)
输出结果:
slice1 : zhongger
slice2 : hongge
这其实是一个浅拷贝,这两个切片的底层数组其实是一样的。
由切片生成的切片,执行了 append 操作
看看下面这段代码
slice1 := []byte("zhongger")
slice2 := slice1[1 : len(slice1)-1]
slice2 = append(slice2, 'z')
fmt.Printf("slice1 : %s \n", slice1)
fmt.Printf("slice2 : %s \n", slice2)
输出结果一定让人感到惊讶
slice1 : zhonggez
slice2 : honggez
为啥会这样呢?这个问题先抛出来给大家,下一篇文章我会具体的分析它的底层逻辑~