宝哥软件园

对PHP数组的深刻理解(遍历顺序)Laruence原创

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

经常有人问我,如果PHP的数组被foreach访问,遍历顺序固定吗?我们将按什么顺序穿越?例如,复制代码如下。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数组是用一个HashTable结构在PHP中实现的,PHP使用了一些机制,使得在O(1)的时间复杂度下添加和删除数组成为可能,同时支持线性遍历和随机访问。HASH在之前的文章中讨论过,PHP的HASH算法,在此基础上,我们做了进一步的扩展。在了解哈希表之前,我们先来看看哈希表的结构定义。我加了注释,方便大家理解:抄码。代码如下: Typedef struct _ Hashtable { uintnablesize;/*哈希表大小,哈希值间隔*/uintnablemask;/*等于nTableSize -1,用于快速定位*/uintnumofelements;/*哈希表中实际元素的数量*/ulong NnextFreeElement;/*下一个可用免费位置的数字索引*/Bucket * pInternalPointer;/*内部位置指针,将被重置、当前*/Bucket * plithead等遍历函数使用;/*头部元素为线性遍历*/Bucket * pListTail;/*尾部元素,用于线性遍历*/Bucket * * abucket;/*实际存储容器*/dtor _ func _ t pDestructor;/*元素的析构函数(指针)*/zend_bool持久;无符号字符nApplyCount/*循环遍历保护*/Zend _ bool Bapplypprotection;#如果ZEND_DEBUG int不一致;#endif }哈希表;关于nApplyCount的意义,我们可以通过下面的例子了解:复制代码。php $arr=array(1,2,3,4,5,$ arr[]=$ arr;var _ export($ arr);//致命错误:嵌套层次太深-递归依赖?设置该字段是为了防止循环引用导致的无限循环。查看上面的结构,我们可以看到HashTable的关键元素是arBuckets,它是实际的存储容器。让我们看看它的结构定义。复制代码如下: Typedef结构桶{ 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是元素的哈希值。对于数字索引的元素,H是直接索引值(nKeyLength=0表示是数字索引)。对于字符串索引,索引值存储在arKey中,索引长度存储在nKeyLength中。在Bucket中,实际数据存储在pData指针指向的内存块中,通常由系统分配。但是有一个例外,就是Bucket保存的数据是指针时,HashTable不会请求系统分配空间来保存这个指针,而是直接将这个指针保存在pDataPtr中,然后将pData指向这个结构成员的地址。这样可以提高效率,减少内存碎片。因此,我们可以看到PHP哈希表设计的精妙之处。如果Bucket中的数据不是指针,则pDataPtr为NULL(此段来自阿尔泰的《Zend HashTable详解》)。结合上面的HashTable结构,我们来解释一下HashTable的总结组成3360

哈希表的pListhHead指向线性列表形式的第一个元素。在上图中,它是元素1,pListTail指向最后一个元素0,对于每个元素,pListNext是红线所画的线性结构的下一个元素。而pListLast是前面的元素。pInternalPointer指向当前内部指针的位置,该位置指示按顺序遍历数组时的当前元素。当线性遍历(按顺序)时,它将从第一个开始,并跟随桶中的第二个/最后一个。根据移动pInternalPointer,我们可以实现所有元素的线性遍历。例如,对于foreach,如果我们查看foreach生成的操作码序列,可以发现在foreach之前,会有一个FE_RESET来重置数组的内部指针。即pInternalPointer(对于foreach,请参考foreach,它深刻理解PHP原理),然后每次用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中遍历数组的顺序与添加元素的顺序有关,所以现在我们知道得很清楚。文章开头问题的输出是:拷贝代码如下:陈辉新2007 2008。因此,如果要根据索引大小遍历数值索引数组,应该使用for代替foreach复制代码如下: for($i=0,$ l=count($ arr);$ I $ l;$i) {//此时不能认为是顺序遍历(线性遍历)}原文:http://www.laruence.com/2009/08/23/1065.html.

更多资讯
游戏推荐
更多+