golang的切片扩容机制是golang面试者绕不开的一扇大门,无论在面试提问,或者面试情景上都绕不开它,今天就说说我理解下的切片扩容。
golang的扩容机制:在go1.18之前有一个临界值为1024,小于1024的时候,切片先两倍扩容,如果两倍扩容后的容量还是不够,就直接以切片需要的容量作为容量。
在go1.18之后,临界值换成了256,小于256和前面相同,大于256公式变为(oldcap+3*256)/4这个公式的值随着oldcap的越来越大,从2一直接近1.25,相对于1.18之前可以更平滑的过渡。
发现问题
看下面一行代码
运行结果如下
出现上面的结果原因是,刚开始切片容量为1,两倍扩容之后,变为2,但是2不够,所以变为所需要的容量3,后面在加4个数字,继续扩容,2倍之后是6,但是6不够应该变为需要的容量,应该为7,为什么是8呢
解决问题
先贴一下源代码,下面第一段代码是扩容机制的基础代码(1.18之前)
下面是造成上面原因的代码
下面是1.18之后的基础扩容机制
从上面代码分析之后,造成7变成8的原因就在这个roundupsize函数上面,它有一个计算公式
ini
复制代码
capmem= roundupsize(uintptr(newcap) * ptrSize)newcap= int(capmem / ptrSize)
其中ptrSize在 64 位机器下的大小为 8。 而此时newcap的值为 7,所以传入到roundupsize函数内部的值为7 * 8 = 56。接着看看roundupsize的内部:
arduino
复制代码
funcroundupsize(size uintptr)uintptr{ifsize < _MaxSmallSize {ifsize <= smallSizeMax-8{returnuintptr(class_to_size[size_to_class8[(size+smallSizeDiv-1)/smallSizeDiv]])}else{//…… } }//……}
yaml
复制代码
const_MaxSmallSize=32768constsmallSizeMax=1024constsmallSizeDiv=8varsize_to_class8=[smallSizeMax/smallSizeDiv+1]uint8{0,1,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13,14,14,15,15,16,16,17,17,18,18,18,18,19,19,19,19,20,20,20,20,21,21,21,21,22,22,22,22,23,23,23,23,24,24,24,24,25,25,25,25,26,26,26,26,26,26,26,26,27,27,27,27,27,27,27,27,28,28,28,28,28,28,28,28,29,29,29,29,29,29,29,29,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,31,31,31,31,31,31,31,31,31,31,31,31,31,31,31,31}varclass_to_size=[_NumSizeClasses]uint16{0,8,16,,24,32,48,64,80,96,112,128,144,160,176,192,208,224,240,256,288,320,352,384,416,448,480,512,576,640,704,768,896,1024,1152,1280,1408,1536,1792,2048,2304,2688,3072,3200,3456,4096,4864,5376,6144,6528,6784,6912,8192,9472,9728,10240,10880,12288,13568,14336,16384,18432,19072,20480,21760,24576,27264,28672,32768}
roundupsize的返回值为uintptr(class_to_size[size_to_class8[(size+smallSizeDiv-1)/smallSizeDiv]]),而:
(size+smallSizeDiv-1)/smallSizeDiv = (56 + 8 - 1) / 8 = 7
size_to_class8[7] = 5
class_to_size[5] = 64所以roundopsize的返回值为 64 ,newcap = int(capmem / ptrSize) = int(64 / 8) = 8,所以最终原切片的容量扩充到了 8。
总结
如果以后遭遇情景题我们按照这个公式算的话很费时间,所以我总结了一下规则,就是如果遇见2倍扩容之后不够需要直接用它所需要的容量的情况,如果是1和3,它的容量可以是1和3,但如果是5,7,9,11等其他的奇数,全部取+1得到离他最近的偶数,因为这个值与class_to_size有关,class_to_size全部存的是8的倍数,把class_to_size里面的值全部除以8,结果为:0,1,2,3,4,6,8,10...........和咱们的规律非常吻合