baiyan介绍案例在PHP7中,我们向数组插入元素的顺序决定了我们遍历数组中元素的顺序。可以说PHP7中的数组是有序的。这个顺序是指元素插入数组的顺序,和遍历中的顺序是一致的。为了直观的让大家理解PHP7数组的顺序,请看下面的PHP代码:$v){var_dump($k.':'.$v);}我们按照1,2,3插入key-valuepair按照循环的顺序放入数组,然后在循环体中打印遍历的顺序,结果如下:string(15)"insert1:baiyan1"string(15)"insert2:baiyan2"string(15)"insert3:baiyan3"然后我们把插入元素的顺序倒过来,按照3,2,1的顺序插入,其余代码不变:$v){var_dump($k.':'.$v);}同理,打印结果如下:string(15)"insert3:baiyan3"string(15)"insert2:baiyan2"string(15)"insert1:baiyan1"观察上面两个多组输出结果,可以看出插入元素的顺序intoarray决定遍历的顺序,PHP数组是有序的。普通哈希表的问题:无序哈希表的无序是指元素插入顺序和遍历顺序不一致。在PHP7中,为了实现查找一个key的复杂度为O(1),内部实现了hashtable结构。抛开PHP的实现不谈,先举一个通用的例子。通常,一个哈希表是这样的,每个存储单元称为一个桶(bucket):这个哈希表很常见,它的大小是8,还没有插入任何元素。接下来我们插入上面三段数据,假设对其key进行hash运算的结果分别为4、2、6,插入后的情况如下(key和value应该已经绑定在一起了,而value的写法为简单省略):大家想一想,像这样存在哪些存储问题:元素分布很分散,扩容或缩容时插入遍历的混乱很难处理。第一项不是我们文章的重点。我们在遍历这个数组的时候,光看这张图,不知道插入的顺序是什么,只能按照insert2、insert1、insert3的顺序遍历。因此,遍历的顺序与插入的insert1、insert2、insert3的顺序不匹配,无法满足我们对PHP7中数组的预期。PHP7数组:解决普通哈希表的乱序问题为了实现插入和遍历的顺序一致,在PHP7中,增加了一个中间映射层,其大小与哈希表相同,元素存放在桶。存储位置,我们称之为映射表。这样说来大家可能不太理解,下面我们用图来一步步重现上一个案例的插入过程。让我们暂时忽略散列冲突的问题。首先,我们插入insert1的键值对:首先,假设对keyinsert1进行哈希运算的结果为4,由于现在哈希表中所有的桶都是空的,我们可以利用第一个桶的空间来存储这个insert1。为了让后续的search等操作能够顺利找到insert1,我们在映射表中的下标4处记录了insert1存放的位置,即桶的下标0。这样在查找的时候,根据hash值4,通过映射表就可以顺利找到insert1的bucket中存放的位置0。然后我们继续插入insert2的键值对。同样的,我们直接去找可用的bucket,下标为1的bucket是可用的,那么我们这里要存储insert2,用映射表记录存储的Bucket下标1:假设keyinsert2的hash运算结果为2,由于下一个可用的bucket下标为1,我们需要记录这个1,它的hash运算结果为2,我们在映射表中标记为2的位置记录了insert2的存储bucket位置1。此时我们可以发现,当我们插入一个新的元素时,我们会直接回头寻找一个可用的bucket位置,而这个位置是和之前插入的元素紧邻的。这样,当我们在foreach循环中直接遍历bucket时,遍历结果是有序的。如果还不明白,我们继续插入insert3的键值对:假设keyinsert3的hash运算结果为6,我们直接寻找一个下标为2的可用桶,我们需要记录下这个2,所以我们可以将这个下标2存储在映射表中标记为哈希值运算结果6的位置。这样,我们直接遍历哈希表,从桶下标0开始到末尾,可以得到和插入时一样的顺序,即insert1、insert2、insert3,元素之间没有分片。提高了hashtable的空间利用率,便于扩容和缩容。至此,我们应该清楚这个映射表的作用了:实现PHP7数组插入和遍历顺序的一致性。在PHP7中,为了方便映射表的访问,映射表的空间并没有单独分配,而是直接分配在哈希表中与上一个块相邻的内存空间中,这样通过一个指针,就可以访问同时映射表和每个桶:在PHP7中,由于映射表的下标为负值,为了实现同样的功能,我们不能直接使用hash值作为下标来存储映射表的位置bucket,但需要经过一步计算:nIndex=h|nTableMask因此,最后我们来看一下PHP中hashtable的结构,最重要的是arData指针。如上图所示,就是中间的垂直分界线。通过访问正负索引数组,我们可以同时访问映射表和哈希表中的桶:struct_zend_array{zend_refcounted_hgc;union{struct{ZEND_ENDIAN_LOHI_4(zend_ucharflags,zend_ucharnApplyCount,zend_ucharnIteratorsCount,zend_ucharconsistency)}v;uint32_t标志;}你;uint32_tnTableMask;桶*arData;//映射表和哈希表的指针,使用arData[-x]访问映射表,使用arData[+x]访问哈希表bucketuint32_tnNumUsed;uint32_tnNumOfElements;uint32_tnTableSize;uint32_tnInternalPointer;zend_longnNextFreeElement;dtor_func_tpDestructor;};typedefstruct_zend_arrayHashTable;健康)状况。至于在PHP中如何解决插入时的hash冲突问题,其实就是用数组的思想来模拟链表,这里就不展开了,我会另开一个专题来讲解未来。
