手机版

理解PHP数组的遍历顺序

时间:2021-10-26 来源:互联网 编辑:宝哥软件园 浏览:

比如:PHP $ arr[' larquence ']='陈辉新';$ arr[' Yahoo ']=2007;$ arr[' Baidu ']=2008;Foreach ($arr as $key=$val) {//结果如何?}再比如:php$arr[2]='陈辉新';$ arr[1]=2007;$ arr[0]=2008;Foreach ($arr as $key=$val) {//现在结果如何?}要充分理解这个问题,我认为首先要了解PHP数组的内部实现结构……

PHP的数组

在PHP中,数组是用一个HashTable结构来实现的,PHP使用了一些机制,使得在O(1)的时间复杂度下添加或删除数组成为可能,同时支持线性遍历和随机访问。HASH在之前的文章中讨论过,PHP的HASH算法,在此基础上,我们做了进一步的扩展。在了解哈希表之前,我们先来看看哈希表的结构定义。我添加了注释,方便大家理解: Typedef struct _ Hashtable { uint able size;/*哈希表大小,哈希值间隔*/uintnablemask;/*等于nTableSize -1,用于快速定位*/uintnumofelements;/*哈希表中实际元素的数量*/ulong NnextFreeElement;/*下一个可用免费位置的数字索引*/Bucket * pInternalPointer;/*内部位置指针,将被重置、当前*/Bucket * plithead等遍历函数使用;/*头部元素为线性遍历*/Bucket * pListTail;/*尾部元素,用于线性遍历*/Bucket * * abucket;/*实际存储容器*/dtor _ func _ t pDestructor;/*元素的析构函数(指针)*/zend_bool持久;无符号字符nApplyCount/*循环遍历保护*/Zend _ bool Bapplypprotection;#if ZEND_DEBUGint不一致;#endif}哈希表;关于nApplyCount的含义,我们可以通过一个例子来理解:php $arr=array(1,2,3,4,5,$ arr[]=$ arr;var _ export($ arr);//致命错误:嵌套层次太深-递归依赖?设置该字段是为了防止循环引用导致无限循环。查看上面的结构,我们可以看到HashTable的关键元素是arBuckets,它是实际的存储容器。我们来看看它的结构定义: TypeDef struct Bucket { ulong h;/*数字索引/哈希值*/uintney长度;/*字符索引的长度*/void * Pdata;/* data */void * pDataPtr;/*数据指针*/struct bucket * plist next;/*线性遍历的下一个元素*/struct bucket * pListLast;/*用于线性遍历的前一个元素*/struct bucket * pNext;/*同一拉链中的下一个元素*/struct bucket *石膏;/*同一拉链中的前一个元素*/char ArKey[1];/*节省内存和方便初始化的提示*/} Bucket;我们注意到最后一个元素是一种灵活的数组技术,可以节省内存并便于初始化。感兴趣的朋友可以谷歌灵活数组. H是元素的Hash值,对于数字索引的元素,H是直接索引值(nKeyLength=0表示数字索引)。对于数字索引,索引的值存储在arKey中,索引的长度存储在nKeyLength中。在Bucket中,实际数据存储在pData指针指向的内存块中,通常由系统分配。但是有一个例外,就是Bucket保存的数据是指针时,HashTable不会请求系统分配空间来保存这个指针,而是直接将这个指针保存在pDataPtr中,然后将pData指向这个结构成员的地址。这样可以提高效率,减少内存碎片。因此,我们可以看到PHP哈希表设计的精妙之处。如果Bucket中的数据不是指针,则pDataPtr为NULL(此段来自阿尔泰的《Zend HashTable详解》)。结合上面的HashTable结构,我们来解释一下HashTable的总结组成3360 HashTable结构示意图

哈希表结构示意图哈希表的头部以线性列表的形式指向第一个元素。在上图中,它是元素1,pListTail指向最后一个元素0,对于每个元素,pListNext是由红色条绘制的线性结构的下一个元素。而pListLast是前面的元素。pInternalPointer指向当前内部指针的位置,该位置指示按顺序遍历数组时的当前元素。当线性遍历(按顺序)时,它将从第一个开始,并跟随桶中的第二个/最后一个。根据移动pInternalPointer,我们可以实现所有元素的线性遍历。例如,对于foreach,如果我们查看foreach生成的操作码序列,可以发现在foreach之前,会有一个FE_RESET来重置数组的内部指针。即pInternalPointer(对于foreach,参考深刻理解PHP原理的foreach),然后每次用FE_FETCH递增pInternalPointer,实现顺序遍历。同样,当我们使用每个/next系列函数进行遍历时,我们也通过移动数组的内部指针来实现顺序遍历。这里有一个问题,比如:php$arr=array(1,2,3,4,5);Foreach ($arr as $v) {//可以获取} while (list ($ key,$ v)=每个($ arr)){//不能获取}?知道了我刚才介绍的知识,这个问题就很清楚了,因为foreach会自动复位,而这一块不会复位,所以foreach完成后,pInternalPointer指向数组的最末端,而语句块当然是不能访问的。解决办法摆在每个人面前。首先,重置数组的内部指针。在随机访问中,头指针在hash数组中的位置将由Hash值决定,然后通过pNext/pLast找到特征元素。添加元素时,元素将被插入到同一哈希元素链的头部和线性列表的尾部。也就是说,当元素被线性遍历时,它们将根据插入的顺序被遍历。这种特殊的设计使得PHP中元素的顺序由添加的顺序决定,而不是索引顺序。也就是说,PHP中遍历数组的顺序与添加元素的顺序有关,所以现在我们清楚地知道,文章开头问题的输出是:huixinchen20072008。如果要根据索引大小遍历数值索引数组,那么应该使用for代替foreachfor($i=0,$ l=count($ arr);$ I $ l;$i) {//此时不能认为是顺序遍历(线性遍历)}

版权声明:理解PHP数组的遍历顺序是由宝哥软件园云端程序自动收集整理而来。如果本文侵犯了你的权益,请联系本站底部QQ或者邮箱删除。