以下为 PHP 数组的基础结构,插入,查找和 rehash 过程。
基础结构:
``` struct _zend_array { zend_refcounted_h gc; union { struct { ZEND_ENDIAN_LOHI_4( zend_uchar flags, zend_uchar nApplyCount, zend_uchar nIteratorsCount, zend_uchar consistency) } v; uint32_t flags; } u; uint32_t nTableMask; // 哈希值计算掩码,等于nTableSize的负值(nTableMask = -nTableSize) Bucket *arData; // 存储元素数组,指向第一个Bucket uint32_t nNumUsed; // 已用Bucket数 uint32_t nNumOfElements; // 哈希表有效元素数 = nNumUsed - num(is_undef) uint32_t nTableSize; // 哈希表总大小,为2的n次方, 最小为8 uint32_t nInternalPointer; // 怀疑是内部指针 zend_long nNextFreeElement; // 下一个可用的数值索引 arr[] = 1;arr["a"] = 2;arr[] = 3; 则nNextFreeElement = 2; dtor_func_t pDestructor; };typedef struct _Bucket {
zval val; // 存储的具体value
zend_ulong h; // hash value (or numeric index)
zend_string *key; // string key or NULL for numerics
} Bucket;
<h3>说明:</h3>
<ul>
<li>数组存放的时候先按照顺序保存 <code>value</code>,再保存 <code>value</code> 的位置。</li>
<li>存放记录的数组称做散列表,这个数组用来存储 <code>value</code>,而 <code>value</code> 按顺序保存,其存储位置会保存在由 <code>key</code> 计算 <code>hash</code> 取模 <code>nTableMask</code> 得到的 <code>idx</code> 中。</li>
<li>数组初始化的时候最小大小为 8,以此为16,32,64。。。</li>
<li>数组初始化的时候边做的 <code>idx</code> 区会全部初始化为 -1,<code>rehash</code> 的时候也会初始化为 -1。</li>
<li>数组中删除一个元素的时候,是把该删除的元素的 <code>type</code> 标记为 <code>is_undef</code>, 并且 <code>nNumOfEmelment - 1</code>,如果该元素为最后一个元素,那么 <code>nNumUsed - 1</code>。</li>
</ul>
<h3>插入:</h3>
<p>以 $arr = ['a'=>1, 'b'=>2] 为例:</p>
<ol>
<li>首先把 1 放到数组中,其 <code>val.u2.next = -1</code>, 根据其下标 <code>a</code> 计算 <code>hash</code>, 然后 <code>hash</code> 取模 <code>nTableMask</code> 得到一个 <code>idx</code>, 在该 <code>idx</code> 的位置保存前边保存 <code>1</code> 的索引 <code>nindex</code>。</li>
<li>再存放 2, 其 <code>val.u2.next = -1</code>, 如果根据其下标 <code>b</code> 计算<code>hash</code> 取模 <code>nTableMask</code> 得到的 <code>idx</code> 中已经有值,那么说明出现了哈希碰撞,这个时候把当前 <code>idx</code> 中的值取出来保存到当前 <code>val.u2.next</code>,把保存 2 的索引 <code>nindex</code> 保存在当前 <code>idx</code>,以此类推。</li>
</ol>
<h3>查找:</h3>
<p>根据下标 <code>a</code> 计算 <code>hash</code> 取模 <code>nTableMask</code> 得到一个 <code>idx</code> ,拿到该 <code>idx</code> 中的值 <code>nindex</code> 去 <code>arData</code> 中查找,如果找到的位置中的 <code>key != a</code>, 那么找不到;如果找到的位置中的 <code>key == a</code>,那么检查其 <code>u2.next</code>, 如果为 -1, 那么找到了;如果不为-1,说明插入的过程中出现了哈希冲突,那么根据 <code>u2.next</code> 继续在 <code>arData</code> 中查找,直到找到为止。</p>
<h3>rehash:</h3>
<p><code>rehash</code> 的时候,首先把 <code>nindex</code> 区的所有记录全部重置为 -1,然后从第一个元素开始挪动指针 <code>*p</code>,如果元素没有被标记为 <code>is_undef</code>,那么重新计算该元素的 <code>key hash</code> 并放到 <code>nindex</code>,然后循环, <code>p++</code>。如果元素被标记为 <code>is_undef</code>, 那么继续挪动指针 <code>p++</code>,并设置一个新的指针 <code>j</code> 指向该位置,继续循环,把后边不为 <code>is_undef</code> 的元素一个一个挪到前边来,<code>p</code> 每次移动,<code>j</code> 遇到 <code>is_undef</code> 就不移动,直到被赋值。一直挪动到最后的 <code>nNunUsed</code> ,那么把 <code>j</code> 赋值给 <code>nNunUsed</code>,之后再插入元素的时候就从这个位置开始插入,以前的元素直接被覆盖就是了。</p>
原文地址:https://segmentfault.com/a/1190000016705316