索引:https://waterflow.link/articles/1666277946416
在go中切片的底层是数组,所以切片的数据间断存储在数组的数据结构中。如果底层的数组满了,切片还须要增加元素的话,底层数组就须要扩容。如果底层数组简直为空时,就会缩容。
在切片外部其蕴含一个指向底部数组的指针、切片的长度、切片的容量。长度是指切片蕴含的元素树,容量底层数组中的元素数。
咱们先看个例子:
s := make([]int, 2, 4)
咱们初始化一个长度为2,容量为4的切片。长度是必选的,容量可选。当不指定容量时,容量和长度雷同,也就是底层数组的长度。
因为长度为2,所以go只初始化了2个元素。咱们能够打印下看下后果:
package mainimport "fmt"func main() { s := make([]int, 2, 4) fmt.Printf("s:%v,len(s):%d,cap(s):%d\n", s, len(s), cap(s))}
go run 4.gos:[0 0],len(s):2,cap(s):4
上面咱们把s[1]设置为1,所以s的第二个元素会被更新为1,s的长度和容量不会变动
咱们看下执行后果:
package mainimport "fmt"func main() { s := make([]int, 2, 4) s[1] = 1 fmt.Printf("s:%v,len(s):%d,cap(s):%d\n", s, len(s), cap(s))}
go run 4.gos:[0 1],len(s):2,cap(s):4
然而当咱们设置s[2] = 1
时会报错,像上面这样:
go run 4.gopanic: runtime error: index out of range [2] with length 2
然而咱们的容量是4,也就是底层数组还有2个闲暇元素。那咱们该如何给剩下的元素赋值呢?
咱们能够应用go的内置函数append,他能够将元素追加到切片的开端。它能够追加一个元素到切片开端:
s = append(s, 1)
也能够追加多个元素到切片开端:
s = append(s, 1, 2)s = append(s, []int{1, 2}...)
还有一种比拟非凡的状况,将字符串追加到字节切片开端:
s = append([]byte("hello"), "world"...)
当初咱们执行append(s, 2)
,将元素2追加到s[1]前面,咱们会失去上面的后果
切片的长度变成了3,容量还是4:
package mainimport "fmt"func main() { s := make([]int, 2, 4) s[1] = 1 s = append(s, 2) fmt.Printf("s:%v,len(s):%d,cap(s):%d\n", s, len(s), cap(s))}
go run 4.gos:[0 1 2],len(s):3,cap(s):4
当初咱们如果间接增加3个元素进去,像上面这样:
s = append(s, 1, 2, 3)
咱们会失去上面的后果:
package mainimport "fmt"func main() { s := make([]int, 2, 4) s[1] = 1 s = append(s, 2) s = append(s, 1, 2, 3) fmt.Printf("s:%v,len(s):%d,cap(s):%d\n", s, len(s), cap(s))}
go run 4.gos:[0 1 2 1 2 3],len(s):6,cap(s):8
咱们发现当初元素的长度变成了6个,这个很好了解,因为咱们总共增加了6个元素。然而切片的容量为什么会是8呢?
这是因为当咱们增加第5个元素时,发现曾经超过切片的容量了。这个时候会触发扩容,会将容量加倍,而后复制所有元素创立另一个数组。而后会把剩下的2和3插进去。
扩容机制:如果不到1024个元素,会成倍扩容;超过1024个元素,按25%扩容
切片中还提供了一种半开区间的赋值形式,保留第一个索引,排除第二个索引,像上面这样:
package mainimport "fmt"func main() { s := make([]int, 2, 4) s[1] = 1 s = append(s, 2) s = append(s, 1, 2, 3) fmt.Printf("s:%v,len(s):%d,cap(s):%d\n", s, len(s), cap(s)) s2 := s[1:2] // 这里从新赋值给s2 fmt.Printf("s2:%v,len(s2):%d,cap(s2):%d\n", s2, len(s2), cap(s2))}
go run 4.gos:[0 1 2 1 2 3],len(s):6,cap(s):8s2:[1],len(s2):1,cap(s2):7
切片s和s2此时是援用同一个底层数组的,然而因为s2是从1开始,所以容量变成了7
这时如果咱们批改了s2[0]或者s[1],实际上他们指向的是底层数组的同一个元素。所以s2[0]或者s[1]都会被批改掉。
package mainimport "fmt"func main() { s := make([]int, 2, 4) s[1] = 1 s = append(s, 2) s = append(s, 1, 2, 3) s2 := s[1:2] s2[0] = 8 // 这里 fmt.Printf("批改俩切片指向的同一个元素:s:%v,len(s):%d,cap(s):%d)----s2:%v,len(s2):%d,cap(s2):%d\n", s, len(s), cap(s), s2, len(s2), cap(s2))}
go run 4.go批改俩切片指向的同一个元素:s:[0 8 2 1 2 3],len(s):6,cap(s):8)----s2:[8],len(s2):1,cap(s2):7
接着咱们持续往s2中插入一个元素,看看会产生什么:
package mainimport "fmt"func main() { s := make([]int, 2, 4) s[1] = 1 s = append(s, 2) s = append(s, 1, 2, 3) s2 := s[1:2] s2[0] = 8 // 插入元素6 s2 = append(s2, 6) fmt.Printf("往s2插入一个元素:s:%v,len(s):%d,cap(s):%d----s2:%v,len(s2):%d,cap(s2):%d\n", s, len(s), cap(s), s2, len(s2), cap(s2))}
go run 4.go往s2插入一个元素:s:[0 8 6 1 2 3],len(s):6,cap(s):8----s2:[8 6],len(s2):2,cap(s2):7
咱们能够看到s2[1]的元素写进去了,长度变为2。然而s2的长度并没有变动,s2[2]的元素却被批改为了6。这是因为往s2插入元素时并没有超过s2的容量,所以还是共用同一个底层数组。
那当初咱们持续往s2中增加6个元素,看看超出容量后的底层数组会是什么样的:
package mainimport "fmt"func main() { s := make([]int, 2, 4) s[1] = 1 s = append(s, 2) s = append(s, 1, 2, 3) s2 := s[1:2] s2[0] = 8 s2 = append(s2, 6) // 持续插入6个元素 s2 = append(s2, 7) s2 = append(s2, 8) s2 = append(s2, 9) s2 = append(s2, 10) s2 = append(s2, 11) s2 = append(s2, 12) fmt.Printf("持续往s2插入6个元素:s:%v,len(s):%d,cap(s):%d----s2:%v,len(s2):%d,cap(s2):%d\n", s, len(s), cap(s), s2, len(s2), cap(s2))}
go run 4.go持续往s2插入6个元素:s:[0 8 6 7 8 9],len(s):6,cap(s):8----s2:[8 6 7 8 9 10 11 12],len(s2):8,cap(s2):14
咱们来剖析下下面的例子:
- 当咱们往s2插入7的时候,此时s2的长度变为3,容量还是7。s[3]对应也被批改。
- 当咱们往s2插入8的时候,此时s2的长度变为4,容量还是7。s[4]对应也被批改。
- 当咱们往s2插入9的时候,此时s2的长度变为5,容量还是7。s[5]对应也被批改。
- 当咱们往s2插入10的时候,此时s2的长度变为6,容量还是7。s[6]对应也被批改。
- 当咱们往s2插入11的时候,此时s2的长度变为7,容量还是7。因为s切片长度为6,所以没有变动。
- 当咱们往s2插入12的时候,此时s2超过s2的容量引发扩容,底层数组被复制,s2指向一个新的容量为14的数组。因为s长度小于容量,所以还是指向原来的数组。
咱们能够通过上面的图片,减少更清晰的意识。