Go语言面试:深入理解切片的基本原理和扩容机制

发表时间: 2024-01-22 11:28

切片扩容机制。

大家好,我是老周。今天通过流程图简单的了解一下切片的扩容机制。

首先来看一个现象,是什么现象?写了几行代码给切片进行扩容。首先定义了一个切片,是个字符串切片给了两个元素,切片肯定就是长度为2,容量为2。然后对它进行扩容,添加三个元素,总共就是五个元素,长度为5,容量是多少?运行一下,长度为5,容量也为5,这是字符串。

接下来改一下类型,比如改成int类型,改成int类型之后再来运行一下,发现扩容之后容量就变成了6。目前是5个元素,但是一个元素是没有填充的,容量为6。而如果改成byte类型,在byte切片再来运行,会发现容量变成了8,这是表象。

切片的扩容机制其实在切片的源码里头是可以看到的。首先根据一定的规则对切片进行计算,计算完成之后再去做什么?再去计算切片。因为切片背后是一个数组,新的数组比如分配了5个元素,比如是int数组,5元素一个int占8个字节,5个元素的数组应该占40个字节,这40个字节就是新的数组,也就是新切片背后的数组需要的内存容量。

但是去做内存分配的时候并不是要多少就刚好给你多少,还有个内存对齐的过程。在go语言里头内存对齐要40个,没有但是要对齐,所以会给48个。48个反过来除以字段的大小就变成6个,也就为什么?int是6个,因为它涉及到内存对齐的过程,而byte切片是8个,同样也是内存对齐的逻辑导致了切片被分配了8个,这就是大体的逻辑。

可以来看一下PPT,首先源码里头这么几个变量的意思。

·第一个就是原来的长度,就是切片原来有多少个,没有扩容之前有多少个。

·第二个原切片的容量,就是原切片的cap。

·第三个就是newLen,前面三个其实都是固定的值,原切片的长度固定的,原切片的容量是固定的,而newLen就等于原切片的长度加下像新附加的元素的个数,就是流量子,就是新切片的长度,而新切片的容量是需要计算的,这就是唯一的变量,这才是需要计算的。

初步得到新切片的容量之后需要去计算cap大小的数值,内存对接或分配的空间大小,这就是cap memory,然后再通过memory的去除以切片元素里面的字段类型的大小,得到真正的newCap,就这么个逻辑。

所以源码也就是这等去做一个初步的计算,初步计算之后根据字段的大小,比如字段切片里面的元素的字段类型,比如是by,那么它就只占一个字节,比如是int或者int64,就走这一块儿。

比如这个是八个字节,四个字节或者八个字节int类型,int类型在32位性能上就是四个字节,在64位性上就是八个字节。其它的比如字段的长,字段的大小满足这种2的幂次,比如八字节是2,三次是6,字节是2的,四字法。这种满足2的幂次的情况下就走这一部分,其它的就走default的部分。这些无非就是为了去做内存分配,找到capmem,就是这一个切片背后的速度到底给它分配多大的内存空间,就是对齐之后分配多大内存空间。

·然后再去将capmem去除以字段大小,得到真正的newCap,就这么一个逻辑。

从流程图上来看,第一步就是newCap等于oldcap,再下一步就是去判断newCap是不是大于2倍的newCap,就是新的切片的长度是不是比原来的切片的大小还大两倍。如果它大两倍,newCap初步计算就是newCap等于newLen,就初步计算。

如果它不是的,就看原来的切片是不是小于256,如果小于256,新的大小就等于两倍的原来大小。而如果它不小于256,就得去判断newCap是不是小于newLen。

如果小于newLen,进行一次循环操作,比如newCap加等于newCap加上3乘以256,最后除以4,就是不断的去为newCap去添加,直到newCap要大于newLen为止。

最后这三种情况都会得到newCap,然后根据newCap和字段类型的size去计算对齐后分配的内存空间,也就是要去计算capmem。最后newCap就等于capmem去除以元素字段类型的siz,int是4或者8,去除以除完之后得到真正的new(。

这就结束了,这就是切片的扩容机制,一键三连,支持了周。