终于理解了Slice扩容机制

警告
本文最后更新于 2020-05-08,文中内容可能已过时。

https://pic.yqqy.top/blog/20200508234639.png
Slice

写了很久的slice却没有去想过他的扩容机制,近期面试,去了解了一下,这次也算是彻底明白了,之前只是简单的看了看文章

先看一个例子

1
2
ints := []int{1, 2}            ---->   扩容前容量 oldCap = 2
ints = append(ints, 3, 4, 5)   ---->   至少扩容到 cap = 5 ?

了解一下预估规则

  • 在原容量扩大两倍还要小于扩容后的容量时,预估容量就是扩容后的

  • 当大于扩容后的时,如果小于1024时,预估容量是扩容前容量的2倍

  • 当大于扩容后的时,如果大于1024时,预估容量是扩容前容量的1.25倍,即以0.25增加

https://pic.yqqy.top/blog/20200508232218.png
扩容规则

所需内存 = 预估容量 * 元素类型大小

这里以int 举例,int在64位操作上默认是int64,即8个字节,所以元素类型不一样,内存也不一样,内存这里需要了解golang的内存管理模块,源码在runtime/sizeclasses.go

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// class  bytes/obj  bytes/span  objects  tail waste  max waste
//     1          8        8192     1024           0     87.50%
//     2         16        8192      512           0     43.75%
//     3         32        8192      256           0     46.88%
//     4         48        8192      170          32     31.52%
...
//    17        256        8192       32           0      5.86%
//    18        288        8192       28         128     12.16%
//    19        320        8192       25         192     11.80%
//    20        352        8192       23          96      9.88%
//    21        384        8192       21         128      9.51%
//    22        416        8192       19         288     10.71%
//    23        448        8192       18         128      8.37%
//    24        480        8192       17          32      6.82%
//    25        512        8192       16           0      6.05%
...
//    66      32768       32768        1           0     12.50%

通过阅读源码可以知道,在小于16字节时,每次以8个字节增加,当大于16小于2^8时,每次以16字节增加,当大于2^8小于2^9时以32字节增加,依此规律…

申请内存时,选择相近的,且大于等于需要的大小

  1. 在之前的例子中,预估容量为5
  2. 1个int是8个字节,5 * 8 = 40
  3. 最合适的内存规格是48,所以48 / 8 = 6
  4. 所以内存会扩容到 6 个容量

下面代码输出什么?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
package main

import "fmt"

func main() {
	a := make([]int, 20)
	b := make([]int, 42)
	a = append(a, b...)
	fmt.Println(len(a), cap(a))
}

长度是62,这无疑,重点是容量计算,两倍的旧容量小于预估计的容量,所以预估计容量成了62, 62 * 8 = 496,在内存规格中选择到了512,所以512 / 8 = 64,即cap(a) = 64。