切片slice
切片是对数组的抽象,数组的长度不可改变,切片是长度可变的动态数组。在go语言中切片的使用是明显高于数组的。
定义的三种方式
1、var 切片名 []type
var s []int
2、使用make()函数定义,make([]type,len)
var s []int = make([]int 0)
3、指定容量make([]type, len, cap),cap为可变参数
var s []int = make([]int,3,4)
结构
type slice struct {
array unsafe.Pointer
len int // 长度
cap int // 容量
}
容量是指底层数组的大小,长度指可以使用的大小
容量的用处是在你用 append 扩展长度时,如果新的长度小于容量,不会更换底层数组,否则,go 会新申请一个底层数组,拷贝这边的值过去,把原来的数组丢掉。也就是说,容量的用途是:在数据拷贝和内存申请的消耗与内存占用之间提供一个权衡。
// 首先声明一个长度为3,容量为4的slice
s :=make([]int,3,4)
// 记录内存地址
fmt.Printf("%p\n",s) // 0xc0000b6000
// 当插入一个元素,slice的长度变为4,所以内存地址不变
s = append(s,4)
fmt.Printf("%p\n",s) // 0xc0000b6000
// 再插入一个元素,slice的长度变为5,已经大于声明变量时限制的容量4,所以进行扩容,更换底层数组,内存地址就改变了
s = append(s,5)
fmt.Printf("%p\n",s) // 0xc0000ba000
s = append(s,6)
fmt.Printf("%p\n",s) // 0xc0000ba000
s = append(s,7)
fmt.Printf("%p\n",s) // 0xc0000ba000
s = append(s,8)
fmt.Printf("%p\n",s) // 0xc0000ba000
// 再次扩容
s = append(s,9)
fmt.Printf("%p\n",s) // 0xc0000bc000
slice操作会影响到底层数组的改变,频繁的内存申请会占用内存,所以在声明slice时尽量预估好容量的大小。slice的扩容规则可参考下面的扩容部分。
使用注意事项
1、由于slice的底层是数组指针,所以一些slice的copy后的操作需要特别注意
s := make([]int, 3)
s = append(s,4,5,6)
fmt.Println(s) // [0 0 0 4 5 6]
a := s
a[3] = 8
// a数据的改变会影响到s中的元素
fmt.Println(s) // [0 0 0 8 5 6]
2、slice可以向后扩展,但不可以向前扩展
s := make([]int, 3)
s = append(s,4,5,6)
fmt.Println(s) // [0 0 0 4 5 6]
a := s[1:4]
fmt.Println(a) // [0 0 4]
// 虽然a中没有5 6这两个元素,但一样可以取到
fmt.Println(a[0:5]) // [0 0 4 5 6]
// 这种取值是不行的
fmt.Println(a[4])
上面的a取了s三个元素,
扩容规则
slice的cap是在用append扩展长度时,控制底层数组的存储是否要发生改变。 下面是golang底层slice扩容时一些比较关键的代码,但并不是扩容的全部代码
newcap := old.cap
doublecap := newcap + newcap
if cap > doublecap {
newcap = cap
} else {
if old.cap < 1024 {
newcap = doublecap
} else {
// Check 0 < newcap to detect overflow
// and prevent an infinite loop.
for 0 < newcap && newcap < cap {
newcap += newcap / 4
}
// Set newcap to the requested cap when
// the newcap calculation overflowed.
if newcap <= 0 {
newcap = cap
}
}
}
上面代码的意思就是
1、如果新的容量大于旧容量的2倍,则直接使用新的容量
s:=make([]int,3,4)
s = append(s,4,5,6,7,8,9)
fmt.Println(cap(s)) // 10
上面本来运行完,cap应该为9,但实际结果为10,其实是go当元素被添加到切片时,当新容量为奇数时,容量增加1
2、如果1不满足,老的容量小于1024,则新的容量直接等于旧容量的2倍
s:=make([]int,3,3)
s = append(s,4)
fmt.Println(cap(s)) // 6
3、如果1不满足,旧容量大于等1024,且旧容量小于需要的容量,则旧容量不停的*1.25,直到大于新的容量
s := make([]int,1024,1024)
// 旧容量1024
fmt.Println(cap(s)) // 1024
s = append(s,1)
// 扩容完 1024 * 1.25 = 1280
fmt.Println(cap(s)) // 1280
add := make([]int,255)
s = append(s,add...)
// 再添加255个元素正好达到1280,容量不变
fmt.Println(cap(s)) // 1280
// 神奇的事情发生了,再次扩容应该为1280 * 1.25 = 1600,但实际为1696
s = append(s,1)
fmt.Println(cap(s)) // 1696
最后一次扩容本应该为1600,但实际为1696,其实slice扩容源码里上面的代码只是比较关键的一部分,后面还有有一些操作,感兴趣的可以深入研究一下。
今天的文章golang slice介绍及扩容规则分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/21623.html