七叶笔记 » golang编程 » golang随手记-slice

golang随手记-slice

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真的是神奇,先来个概念

  1. nil切片:len=0 cap=0 pointer=0
  2. 空切片: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)
}  

相关文章