Slice常见操作及底层原理实现
一 什么是Slice
- slice(切片)是一种数组结构,相当于是一个动态的数组,可以按需自动增长和缩小。
- 那么为什么需要slice呢?
- 在GO语言中,数组是一个值,在进行传参和赋值操作时,都会将数组拷贝一份,当数组较大时耗费较多资源;使用数组的指针会较为麻烦
- slice是引用类型,传参时不需要再用到指针;slice本质上是数组的指针,所以传参时不需要拷贝数组,耗费较小;可以动态改变数组大小,使用更加的方便
二 Slice的常用操作
-
slice的创建
-
a. 使用内置的make函数
//只指定长度,则默认容量和长度相等 //容量的定义在后面解释 slice := make([]string, 5) //指定长度和容量,容量不能小于长度 slice = make([]string, 3, 5)
-
b. 使用切片字面量
//其长度和容量都是3 s1 := []string{"dog", "cat", "bear"} //使用索引声明切片 //下面创建了一个长度为100的切片 s2 := []int{99: 0}
-
c. nil和空切片
-
声明时不做任何初始化就会创建一个nil切片
var slice []int //new 产生的是指针,需要用* slice := *new([]int)
-
声明空切片
//使用make s1 := make([]int, 0) //使用切片字面量 s2 := []int{}
-
空切片与nil切片的区别:
- nil切片=nil, 而空切片!=nil,在使用切片进行逻辑运算时尽量不要使用空切片
- 空切片指针指向一个特殊的zerobase地址,而nil为0
- 在JSON序列化有区别:nill切片为{“values":null}, 而空切片为{"value" []}
-
-
-
增加元素
- 使用内置函数append添加元素
-
复制切片
- 使用copy
-
删除元素,内置没有提供,下面简单实现一下:
func deleteSlice(index int, s []int) []int{ s1 := s[:index] s1 = append(s1, s[index+1:]...) return s1 }
三 slice底层实现
-
切片是基于数组实现的,是数组的抽象,因此底层的内存是连续的,效率较高,效率非常高,还可以通过索引获得数据,可以迭代以及垃圾回收优化的好处。切片对象本身的很小,是因为它是只有3个字段的数据结构:一个是指向底层数组的指针,一个是切片的长度,一个是切片的容量。这3个字段,就是Go语言操作底层数组的元数据。查看其源码如下:
type notInHeapSlice struct { array *notInHeap //底层数组的指针 len int //切片的长度 cap int //切片的容量 }
slice底层结构.png
-
关于容量与长度:
-
a. 长度:slice当前元素个数
容量:底层数组的空间,当容量不足时会开辟新的数组空间,避免频繁开辟内存空间
-
b. 计算:对于底层数组容量是k的切片slice[i,j]来说
- 长度:j-i
- 容量:k-i
c. 数组索引不能超过长度
-
-
切片增长会改变长度,容量不一定,需要看可用容量,当容量不足时会分配一个新的底层数组,将现有的值复制到新数组再添加新的值。
s := make([]int, 0, 5) t := append(s, 1,2,3) fmt.Printf("before_arr_s = %p\n", s) fmt.Println(t) fmt.Printf("before_arr_t = %p\n", t) t = append(t, 1,2,3) fmt.Println(s) fmt.Printf("after_arr_s = %p\n", s) fmt.Println(t) fmt.Printf("after_arr_t = %p\n", t) -------------------------- [] before_arr_s = 0xc000078030 [1 2 3] before_arr_t = 0xc000078030 [] after_arr_s = 0xc000078030 [1 2 3 1 2 3] //当第二次添加元素时超过了容量限制,于是重新开辟了数组,查看地址发现的确发生了改变 after_arr_t = 0xc00008c000
四 拓展:三个索引的切片
第三个索引可以限定容量。对于slice[i:j:k],长度=j-i,容量=k-i
-
在创建切片时设置切片的容量和长度一样,可以强制让新切片的第一个append操作创建新的底层数组,与原有的底层数组分类。保持数组的简洁,更加的安全。
- a. 若不限定分片的容量,直接append的话可能会覆盖底层数组,从而影响到其他切片,出现奇怪的bug
func main(){ b := []int{1, 2, 3, 4, 5, 6} c := b[: 2] c = append(c, 7) fmt.Println(b) fmt.Println(c) } ---------------------- //b切片被c影响 [1 2 7 4 5 6] [1 2 7]
- b. 在使用切片时限定容量可以避免上述情况
func main(){ b := []int{1, 2, 3, 4, 5, 6} c := b[: 2:2] c = append(c, 7) fmt.Println(b) fmt.Println(c) } ------------------ //在使用切片时限定容量,c切片append时开辟了新的数组,不影响原数组上的切片 [1 2 3 4 5 6] [1 2 7]
更多Go的相关文章发布在我的个人博客上,欢迎访问
www.guiguiyo.cn