Golang切片
什么是切片(slice)
简单的说,数组切片就像一个指向数组的指针,实际上它拥有自己的数据结构,而不仅仅是个指针。数组切片的数据结构可以抽象为以下3个变量:
1>.一个指向原生数组的指针(point):指向数组中slice指定的开始位置;
2>.数组切片中的元素个数(len):即slice的长度;
3>.数组切片已分配的存储空间(cap):也就是slice开始位置到数组的最后位置的长度。
切片的结构
ptr指向真正的array,在64位的机器上,golang切片长度24位,指针8位,长度8位,容量8位
切片扩容机制
- 切片每次新增个数不超过原来的1倍,且每次增加数不超过1024个,且增加后总长度小于1024个,这种情况下扩容后为原来的2倍
func capTest(){
s1 := make([]int, 0)
fmt.Printf("The capacity of s1: %d\n", cap(s1))
for i := 1; i <= 33; i++ {
s1 = append(s1, i)
fmt.Printf("s1(%d): len: %d, cap: %d\n", i, len(s1), cap(s1))
}
fmt.Println()
}
结果:
-
切片一次新增个数超过原来1倍,但不超过1024个,且增加后总长度小于1024个,这种情况下扩容后比实际具有的总长度还要大一些。
func capTest(){ s2 := make([]int, 10) fmt.Printf("The capacity of s2: %d\n", cap(s2)) r1 := append(s2, make([]int, 5)...) fmt.Printf("r1: len: %d, cap: %d\n", len(r1), cap(r1)) r2 := append(s2, make([]int, 11)...) fmt.Printf("r2: len: %d, cap: %d\n", len(r2), cap(r2)) r3 := append(s2, make([]int, 21)...) fmt.Printf("r3: len: %d, cap: %d\n", len(r3), cap(r3)) fmt.Printf("注意:像r2,r3 一次增加个数超过原容量的1倍,增加后结果比实际总长度预想的稍大一点 \n") fmt.Println() }
结果:
-
原切片长度超过1024时,一次增加容量不是2倍而是0.25倍,每次超过预定的都是0.25累乘
// src/runtime/slice.go // go version 1.13 func growslice(et *_type, old slice, cap int) slice { // ...省略部分 newcap := old.cap doublecap := newcap + newcap if cap > doublecap { newcap = cap } else { //原切片长度低于1024时直接翻倍 if old.len < 1024 { newcap = doublecap } else { // Check 0 < newcap to detect overflow // and prevent an infinite loop. //原切片长度大于等于1024时,每次只增加25%,直到满足需要的容量 for 0 < newcap && newcap < cap { newcap += newcap / 4 } // Set newcap to the requested cap when // the newcap calculation overflowed. if newcap <= 0 { newcap = cap } } } // ...省略部分 }
切片扩容的坑
func SliceTest1(){
s1:=make([]int,4)
for i:=0;i<4;i++{
s1[i] = i+1
}
s2 := s1
fmt.Println("--------------------------------------")
fmt.Printf("s1指向地址:%p,cap is %d\n",s1,cap(s1))
fmt.Printf("s2指向地址:%p,cap is %d\n",s2,cap(s2))
fmt.Printf("s1真实地址:%p\n",&s1)
fmt.Printf("s2真实地址:%p\n",&s2)
fmt.Println("--------------------------------------")
s2 = append(s2, 13)
fmt.Printf("s1指向地址:%p,cap is %d\n",s1,cap(s1))
fmt.Printf("s2指向地址:%p,cap is %d\n",s2,cap(s2))
fmt.Println("--------------------------------------")
s2[2] = 9
fmt.Println("s1=",s1)
fmt.Println("s2=",s2)
}
执行结果:
可以看出来因为 s2:=s1导致s2跟s1的ptr指向了同一个地方,但是s2 append之后导致了s2扩容,扩容之后s2指向的地址改变,所以对s2的改动不会对s1有任何的影响
接下来我们做点更改:
把s1的容量设置大一点:
func SliceTest1(){
s1:=make([]int,4,6)
for i:=0;i<4;i++{
s1[i] = i+1
}
s2 := s1
fmt.Println("--------------------------------------")
fmt.Printf("s1指向地址:%p,cap is %d\n",s1,cap(s1))
fmt.Printf("s2指向地址:%p,cap is %d\n",s2,cap(s2))
fmt.Printf("s1真实地址:%p\n",&s1)
fmt.Printf("s2真实地址:%p\n",&s2)
fmt.Println("--------------------------------------")
s2 = append(s2, 13)
fmt.Printf("s1指向地址:%p,cap is %d\n",s1,cap(s1))
fmt.Printf("s2指向地址:%p,cap is %d\n",s2,cap(s2))
fmt.Println("--------------------------------------")
s2[2] = 9
fmt.Println("s1=",s1)
fmt.Println("s2=",s2)
}
结果如下:
可以看见这次没有发生扩容,因为我们设置了容量为6,对s1,s2任意一个做出改变,另一个也会更改,再s2[2]=9前面加一个s1[1] = 7,看看结果如何
不出意料,都改变了,说明了他们其实就是同一份,没有扩容指向的是同一个地方。
接下来我们再做一点骚操作,我们先思考一下,既然是同一个地方,那为什么s2多了一个值呢?而s1为什么还是原来的样子?
稍微改一下代码:
func SliceTest1(){
s1:=make([]int,4,6)
for i:=0;i<4;i++{
s1[i] = i+1
}
s2 := s1
fmt.Println("--------------------------------------")
fmt.Printf("s1指向地址:%p,cap is %d\n",s1,cap(s1))
fmt.Printf("s2指向地址:%p,cap is %d\n",s2,cap(s2))
fmt.Printf("s1真实地址:%p\n",&s1)
fmt.Printf("s2真实地址:%p\n",&s2)
fmt.Println("--------------------------------------")
s2 = append(s2, 13)
s1 = append(s1,100) // 新增
fmt.Printf("s1指向地址:%p,cap is %d\n",s1,cap(s1))
fmt.Printf("s2指向地址:%p,cap is %d\n",s2,cap(s2))
fmt.Println("--------------------------------------")
fmt.Println("s1=",s1)
fmt.Println("s2=",s2)
}
我们在s2 = append(s2, 13)的下一行增加了s1 = append(s1,100)再看看结果如何:
嗯????我s2的13怎么变成了100???
将这两行换个位置试试:
我的100又去哪儿了?
其实原因是s1,s2使用的是同一块内存,append也是在原来的地址后面append,所以这两次append其实改变的是底层数组的同一个位置而已,后面的修改自然会覆盖前一次的修改,
拿后面的一次举例,s1 append之后长度就变了但是并没有扩容:
其实这个时候s2也已经变了,假如你能越界访问s2的第五个元素,但是会报越界错误,当你给s2 append的时候同样修改的是第五个位置,用13进行覆盖,这时两个切片的长度都是5,所以都可以访问到,而且是后面修改后的值
推荐一篇文章:https://newt0n.github.io/2016/11/07/%E5%A6%82%E4%BD%95%E9%81%BF%E5%BC%80-Go-%E4%B8%AD%E7%9A%84%E5%90%84%E7%A7%8D%E9%99%B7%E9%98%B1/#%E6%95%B0%E7%BB%84-Arrays
上一篇: 使用css实现三角符号
下一篇: #python 切片