我们已经准备好了,你呢?

2024我们与您携手共赢,为您的企业形象保驾护航!

经常有人问,如果使用PHP数组来访问,遍历的顺序是不是固定的?应该按照什么顺序来遍历?

比如:
 $val) {
 //结果是什么?
}

另一个例子:

 $val) {
 //现在结果又是什么?
}

为了彻底理解这个问题,我认为我们应该首先了解PHP数组的内部实现结构...

PHP 数组

在PHP中,数组是使用HASH结构()实现的。PHP使用一些机制来实现O(1)时间复杂度的数组添加和删除,并且同时支持线性遍历和随机访问。

上一篇文章也讨论了PHP的HASH算法,在此基础上我们再进行进一步的扩展。

在了解之前我们先来看一下结构体定义,为了让大家更容易理解我添加了注释:

typedef struct _hashtable {
 uint nTableSize; /* 散列表大小, Hash值的区间 */
 uint nTableMask; /* 等于nTableSize -1, 用于快速定位 */
 uint nNumOfElements; /* HashTable中实际元素的个数 */
 ulong nNextFreeElement; /* 下个空闲可用位置的数字索引 */
 Bucket * pInternalPointer; /* 内部位置指针, 会被reset, current这些遍历函数使用 */
 Bucket * pListHead; /* 头元素, 用于线性遍历 */
 Bucket * pListTail; /* 尾元素, 用于线性遍历 */
 Bucket ** arBuckets; /* 实际的存储容器 */
 dtor_func_t pDestructor; /* 元素的析构函数(指针) */
 zend_bool persistent;
 unsigned char nApplyCount; /* 循环遍历保护 */
 zend_bool bApplyProtection;
 #if ZEND_DEBUG
 int inconsistent;
 #endif
} HashTable;

我们可以通过一个例子来理解的含义:

设置该字段是为了防止循环引用导致的无限循环。

查看上面的结构体,我们可以看到关键元素是 ,也就是实际的存储容器,我们来看一下它的结构定义:

typedef struct bucket {
ulong h; /* 数字索引/hash值 */
uint nKeyLength; /* 字符索引的长度 */
void *pData; /* 数据 */
void *pDataPtr; /* 数据指针 */
struct bucket *pListNext; /* 下一个元素, 用于线性遍历 */
struct bucket *pListLast; /* 上一个元素, 用于线性遍历 */
struct bucket *pNext; /* 处于同一个拉链中的下一个元素 */
struct bucket *pLast; /* 处于同一拉链中的上一个元素 */
char arKey[1]; /* 节省内存,方便初始化的技巧 */
} Bucket;

我们注意到最后一个元素是一个数组trick,可以节省内存,也方便初始化,有兴趣的朋友可以使用数组。

h 是元素的哈希值。对于具有数字索引的元素,h 是直接索引值(使用 =0 表示数字索引)。对于字符串索引,索引值存储在 arKey 中,索引长度存储在 中。

在 中,实际的数据是存放在 pData 指针指向的内存块中的,这个内存块一般由系统分配。但有一个例外,就是当存储的数据是指针时,系统不会请求分配空间来存储这个指针,而是直接将指针保存在 中,然后 pData 会指向这个结构体成员的地址。这样可以提高效率,减少内存碎片。从中可以看出 PHP 设计的精妙之处,如果 中的数据不是指针,则为 NULL(此段来自《Zend 详解》)

结合上面的结构,我们来解释一下下面的整体结构图:

php while list each_php while list each_php while list each

有点粗糙

指向线性列表形式中第一个元素的指针,在上图中它是元素1,指向最后一个元素的指针0。对于每个元素,它是红线绘制的线性结构的下一个元素,以及前一个元素。

指向当前内部指针位置。顺序遍历数组时,该指针指向当前元素。

线性(顺序)遍历的时候,我们从最开始,按照中间的/,移动,实现线性遍历所有元素。

比如对于 ,我们查看它生成的序列可以发现,在之前,会对数组内部的指针进行一次重置,也就是(详细信息参见深入理解PHP原理),然后每次都进行递增,从而实现顺序遍历。

同样的,我们在使用each/next系列函数遍历的时候,也是通过移动数组内部指针来实现顺序遍历的,这里就存在一个问题,比如:


知道了刚才介绍的,问题就很明确了,因为会自动重置,而while语句块不会重置,所以结束之后就指向了数组的末尾,while语句块是无法访问到的。解决办法就是在each之前重置一下数组的内部指针。

在进行随机访问时,通过hash值确定hash数组中头指针的位置,再通过pNext/pLast寻找特征元素。

添加元素时,会将其插入到同一个 Hash 元素链的头部和线性列表的尾部。也就是说,在线性遍历时,元素会按照插入的顺序进行遍历。这种特殊的设计使得在 PHP 中,当使用数字索引时,元素的顺序由添加顺序决定,而不是索引顺序。

也就是说 PHP 中数组的遍历顺序是和元素添加的顺序有关的,现在我们清楚的知道了文章开头那道题的输出是:

huixinchen
2007
2008

因此,如果你想按索引大小迭代数值索引数组,则应使用 for 而不是

for($i=0,$l=count($arr); $i<$l; $i++) {
 //这个时候,不能认为是顺序遍历(线性遍历)
}

php while list each_php while list each_php while list each

二维码
扫一扫在手机端查看

本文链接:https://by928.com/4146.html     转载请注明出处和本文链接!请遵守 《网站协议》
我们凭借多年的网站建设经验,坚持以“帮助中小企业实现网络营销化”为宗旨,累计为4000多家客户提供品质建站服务,得到了客户的一致好评。如果您有网站建设、网站改版、域名注册、主机空间、手机网站建设、网站备案等方面的需求,请立即点击咨询我们或拨打咨询热线: 13761152229,我们会详细为你一一解答你心中的疑难。

项目经理在线

我们已经准备好了,你呢?

2020我们与您携手共赢,为您的企业形象保驾护航!

在线客服
联系方式

热线电话

13761152229

上班时间

周一到周五

公司电话

二维码
微信
线