由浅入深聊聊Golang的slice
Posted 咖啡色的羊驼
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了由浅入深聊聊Golang的slice相关的知识,希望对你有一定的参考价值。
前言
今天本来想去外地玩耍,结果睡过头错过了动车,只好总结一下slice,希望能与slice之间做一个了断。
文章由浅入深,遵从能用代码说话就不bb的原则。
正文
1.基本操作
1.1 声明
var stringSlice []string
stringSlice := []string"咖啡色的羊驼"
var intSlice []int64
intSlice := []int18
和数组的区别:就是**[]括号里头不加东西**。
初始化的的一些默认值:
func main()
var stringSlice []string
var intSlice []int64
fmt.Printf("stringSlice ==> 长度:%v \\t地址:%p \\t零值是否nil:%v \\n",len(stringSlice),stringSlice, stringSlice==nil)
fmt.Printf("intSlice ==> 长度:%v \\t地址:%p \\t零值是否nil:%v",len(intSlice),intSlice, intSlice==nil)
这里需要注意的是:slice的key必须是数字 && 0开始逐渐增加
1.2 增删改查
// 增
func add(slice []interface, value interface) []interface
return append(slice, value)
// 删
func remove(slice []interface, i int) []interface
return append(slice[:i], slice[i+1:]...)
// 改
func update(slice []interface, index int, value interface)
slice[index] = value
// 查
func find(slice []interface, index int) interface
return slice[index]
这里需要注意的是:
1.slice的增加需要依赖于append,这里会涉及到扩容机制(后文会说)
2.删除的话,只能是通过切割的方式重拼了,由于slice是引用类型,存的是指针,性能上不会有太多影响
1.3 插入 & 遍历 & 清空
// 插入
func insert(slice *[]interface, index int, value interface)
rear := append([]interface, (*slice)[index:]...)
*slice = append(append((*slice)[:index], value), rear...)
// 遍历
func list(slice []interface)
for k, v := range slice
fmt.Printf("k:%d - v:%d", k,v)
// 清空
func empty(slice *[]interface)
*slice = append([]interface)
// *slice = nil
1.3 复制
// 复制
func main()
intSlice := []int1,2,3,4,5,6
copySlice1 := make([]int,0,10)
_ = copy(copySlice1,intSlice)
fmt.Printf("长度为0的时候:%v\\n",copySlice1)
copySlice2 := make([]int,6,10)
_ = copy(copySlice2,intSlice)
fmt.Printf("长度为6的时候:%v",copySlice2)
这里需要注意的是:要保证目标切片有足够的大小,注意是大小,而不是容量。
2.slice的深入了解
2.1 slice的基础数据结构 & 图
slice的基础数据结构:
type slice struct
array unsafe.Pointer
len int
cap int
字段 | 说明 |
---|---|
array | 指向指针,指向一个底层数组 |
len | slice中元素的个数 |
cap | slice的容量,允许slice中元素增长的最大个数 |
这里的array需要单独说下,这里是指针类型,也说明了slice是引用类型。
在slice底层,指针指向的是另一个数组。
还是有必要看一下源码中的实现:
// 创建一个slice
func makeslice(et *_type, len, cap int) slice
// 检查目标类型的最长长度,slice的len和cap都必须小于这个值
maxElements := maxSliceCap(et.size)
if len < 0 || uintptr(len) > maxElements
panic(errorString("makeslice: len out of range"))
// !!! len必须<= cap !!!
if cap < len || uintptr(cap) > maxElements
panic(errorString("makeslice: cap out of range"))
// 申请内存
p := mallocgc(et.size*uintptr(cap), et, true)
// 返回一个slice
return slicep, len, cap
2.2 slice切割的底层变化
func main()
x:=[]int1,2,3,4
y:=x[1:4]
fmt.Println(y)
// 输出:[2 3 4]
这里需要注意的是:切割遵从左开右闭的原则,就是[1:4],取得是第二个元素到第四个以下的,不包括第四个
来一波图解:
2.3 slice的扩容机制 & 实战例子
slice扩容机制还是比较有意思的,上源码:
func growslice(et *_type, old slice, cap int) slice
...
newcap := old.cap
doublecap := newcap + newcap
if cap > doublecap
newcap = cap
else
if old.len < 1024
newcap = doublecap
else
for newcap < cap
newcap += newcap / 4
...
return slicep, old.len, newcap
白话文讲解:如果新cap的大小是当前2倍以上,则增长到新的cap大小;否则,如果当前cap大小<1024,则按照2倍增长,不然就每次就按照当前大小的1/4增长,直到大小>=新的cap大小。
前文很多操作都是基于append的,那么slice在append的时候,如果发生了扩容,那么底层的数组会重建,同时拷贝老的数据到新数组里头。
举个例子:
func main()
initSlice := []int1
// 进行扩容到2
initSlice = append(initSlice, 2)
// 进行扩容到4
initSlice = append(initSlice, 3)
x := append(initSlice, 4)
y := append(initSlice, 5)
fmt.Println(initSlice, x, y)
会输出:
[1 2 3] [1 2 3 5] [1 2 3 5]
图解说明:
根据前文介绍的扩容机制,initSlice的扩容轨迹是1-2-4。而slice只是引用类型,所以x和y只是copy了initSlice的指针,他们三个都是指向同一个底层数组,所以最后第四个坑被y给覆盖了。
再举一个正好遇到扩容时候的例子:
我们知道扩容时候是会生成新的底层数组,然后拷贝老的数组值。
func main()
initSlice := []int1
// 此时扩容1-2,并且全部装满
initSlice = append(initSlice, 2)
// 以下任一append都会引发扩容
x := append(initSlice, 3)
y := append(initSlice, 4)
fmt.Println(initSlice, x, y)
输出:
[1 2] [1 2 3] [1 2 4]
图解:
由于都遇到了扩容,所以x与y各自另立门户,新建数组,slice指向的底层数组也不同了所以互不干扰了。
3.注意点
3.1 函数传参
func main()
initSlice := []int1,2,3
fmt.Printf("刚开始时候:%v\\n",initSlice)
doSomeThing(initSlice)
fmt.Printf("一番操作后:%v\\n",initSlice)
func doSomeThing(s []int)
s[0]=88
s = append(s, 10)
fmt.Printf("函数返回前:%v\\n",s)
输出:
刚开始时候:[1 2 3]
函数返回前:[88 2 3 10]
一番操作后:[88 2 3]
这里面的变化情况抓住一个点:就是发送扩容时候底层数组是新建的!
然而我想说的是:函数传slice,由于是引用类型,所以是会改变原值的,这时候需要考虑扩容新底层数组问题。
如果你觉得有收获~可以关注我的公众号【咖啡色的羊驼】~第一时间收到我的分享和知识梳理~
以上是关于由浅入深聊聊Golang的slice的主要内容,如果未能解决你的问题,请参考以下文章