Lua向表内新增元素(N)主要走的是luaH_newkey函数。其过程大体如下:
根据给定的key计算出应该存放的位置P
- 如果P处没有元素,直接存放就好
- 如果P处已经有元素,这时存在两种情况:
- 在P处发生了碰撞,需要在物理上将N存放到下一个空位,并从逻辑上将N连接到P的链表中
- 在别处放生碰撞的元素(O)被放到P处,需要将O移到下一个空位,将N存放到P处
可以看到,无论何处发生的碰撞导致P处被占据,此时都要找到一个新的空位置F来存放新元素N,而当空间已满的时候,无法找到新的F,此时就会触发重散列的过程,调用rehash方法。
我们知道,Lua的表包括了数组和散列表两部分,这两部分的大小如何分配、整数键的元素是放到数组中还是放到散列表中,都是由Lua自行决定的。这个自行决定的完整过程,就是rehash方法的内容。
Lua分配这两部分空间的原则也很简单,即优先计算数组部分,剩下的部分全放到散列表里。而如何决定数组部分的大小,Lua的作者们在《The Implementation of Lua 5.0》中已经说得很明白了:
- 数组的空间至少有一半以上被利用(保证空间的利用率)
- 数组的后半部分内至少有一个元素(避免只要一半空间就能装满却多浪费了一倍空间的情况)
为此,我们在重散列的过程中需要知道:
- 一共有多少个有效的整数key(value不为空)—— 变量na
- 这些整数key的分布情况 —— nums数组
- 所有的key的数量(用于计算散列表部分的容量)—— 变量totaluse
所以,rehash的逻辑也很清晰:
- 通过numusearray统计数组部分内包含的整数key数量和分布情况
- 通过numusehash统计散列表部分内包含的整数key数量和分布情况,并得到总共key的数量
- 通过computesizes计算应该分配给数组部分的空间大小
- 通过luaH_resize按照之前计算的结果重新分配空间