panic
a:=make([]int,3,4)
a[0] = 1
a[1] = 2
a[2] = 3
a[3] = 4
panic: runtime error: index out of range [4] with length 3
虽然a的容量4,但是由于一开始初始化的时候指定的slice的长度为3,那么a[3]就会发生越界,发生panic。那么这个4是干啥的?
结构
type slice struct {
array unsafe.Pointer
len int
cap int
}
- slice的底层是个数组,所以有个pointer(指向底层数组的指针)
- slice有自己的长度len
- slice有自己的容量cap
扩容
cap的作用就是用在扩容上的,如果你预期你的slice可能随着程序的运行要塞很多数据进去,那么你可以在make的时候的,把cap设置的大点,避免在扩容的时候的频繁的申请内存,造成性能损耗。先看下源码
newcap := old.cap
doublecap := newcap + newcap
if cap > doublecap {
newcap = cap
} else {
if old.len < 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
}
}
}
这是slice扩容的比较核心的代码,掌握透可以吊打面试官。这是go1.15的代码,较老的版本可能存在差异。 通过阅读源码,可以总结:
s:=make([]int,3,3)
s = append(s,4,5,6,7)
fmt.Println(cap(s))
初始化cap=3,连续append 4个元素,需要的新容量=7,但是此时cap*2=6,所以按照代码应该是7,但是测试下来发现是8。这是因为go还有后续逻辑,判断如果此时是奇数,那么就加1。
2. 如果1不满足,如果老的元素个数小于1024,那么新的容量就是原来的两倍
s:=make([]int,3,3)
s = append(s,4)
fmt.Println(cap(s))
此时只扩容了一个,理论新的容量需要至少4,小于2*cap=6,所以结果就是8。
3. 如果1不满足,且老的元素个数大于等于1024,老的cap不停的1.25倍,直到大于新的容量
s:=make([]int,1024)
add:=make([]int,570)
s = append(s,add...)
fmt.Println(cap(s))
1024+570=1594 1024*1.25=1280<1594,所以还要运算一次 1280*1.25=1600>1594,所以此时的cap=1600? 然而事实是1696,后台通过debug,发现上述核代码处算出的的确是1600,但是此时还没走完。
case et.size == sys.PtrSize:
lenmem = uintptr(old.len) * sys.PtrSize
newlenmem = uintptr(cap) * sys.PtrSize
capmem = roundupsize(uintptr(newcap) * sys.PtrSize)
overflow = uintptr(newcap) > maxAlloc/sys.PtrSize
newcap = int(capmem / sys.PtrSize)
这段代码走完就是1696了。
make与new
- make只能用于slice、map、chan 返回的是对象的本身,可以直接操作对象
- new的定义传参是数据类型, 可以是int、string、struct 、map 、slice 返回的不是对象本身,而是指向对象的指针
func new(Type) *Type
奇怪操作:
aa:=new([]int)
*aa = append(*aa,1)
fmt.Println((*aa)[0]) // 1
空切片与nil切片
不得不说go真的是神奇,先来个概念
- nil切片:len=0 cap=0 pointer=0
- 空切片:len=0 cap=0 pointer!=0
什么情况会创建nil和空的slice
nil | 空 |
var a []int | a:=make([]int,0) |
a:=*new([]int) | a:=[]int{} |
官方建议:用nil的slice
var a1 []int
a2:=*new([]int)
a3:=make([]int,0)
a4:=[]int{}
fmt.Println(*(*[3]int)(unsafe.Pointer(&a1))) //[0 0 0]
fmt.Println(*(*[3]int)(unsafe.Pointer(&a2))) //[0 0 0]
fmt.Println(*(*[3]int)(unsafe.Pointer(&a3))) //[824634109648 0 0]
fmt.Println(*(*[3]int)(unsafe.Pointer(&a4))) //[824634109648 0 0]
空切片的地址都一样,指向一个没有空间的地址
var zerobase uintptr
if size == 0 {
return unsafe.Pointer(&zerobase)
}