php foreach 可以遍历数组吗,PHP foreach 是如何遍历数组的

介绍了array在PHP中的结构(zval):HashTable.

然后罗列了若干test case,从不同角度去理解foreach的机制:

reset();

while (get_current_data(&data) == SUCCESS) {

move_forward();

code();

}

结论是:

1) 数组的refcount__gc为1,is_ref__gc为0,那么foreach并不会复制zval;

2) 数组的refcount__gc>1,is_ref__gc为0,那么foreach将会复制zval;

3) 数组的is_ref__gc为1,那么foreach并不会复制zval;

看完文章之后,我对结论 2) 提出了更深层次的疑问,也就是作者文中的[附加的一个问题]:

有人问道当foreach发生zval复制时,从上面的例子可以得出这样的结论:(zval).value->ht会被复制一份,那么(zval).value->ht->arBuckets即该二级指针存储的Bucket是否也会被复制?

凭直觉,arBuckets是不应该被复制的,因为它的值并未发生变化,被复制的应该只是ht,而且复制整个数组其实开销非常大,当然这只是我的直觉罢了。

但是作者在文中使用了一个test case证明arBuckets也被复制了。可是我总觉得他用的这些test case有问题,最近忙着辞职,也没细想。

今天早上在路上又想了一下,其实可以使用memory_get_usage()函数来不断监测php脚本占用内存的情况, 从而来监测是否发生了arBuckets的复制。

在实验的过程中,发现,作者使用current()函数对foreach的分析都是错误的,zval的复制与否,并不是由foreach控制的,而是由current()控制的。

实验环境:

64位 php5.3.10

首先来看只有foreach的时候, 内存的使用情况:

print "INIT:".memory_get_usage().PHP_EOL;

$arr = range(1,2000);

print "FIRST ARRAY:".memory_get_usage().PHP_EOL;

$ref = $arr;

print "ref_count+1:".memory_get_usage().PHP_EOL;

foreach ($arr as $val)

{

echo $val;

}

print PHP_EOL;

print "after foreach:".memory_get_usage().PHP_EOL;

print PHP_EOL;

输出:

INIT:626288

FIRST ARRAY:915000 //第一个数组创建出来之后,内存增加200多K。

ref_count+1:915096 //$ref = $arr, 只是ref_count+1, ht并未被复制,arBuckets也没有被复制,内存增加 96字节,可能是消耗在全局符号表上面了(求详细解释)。

after foreach:915192 //foreach循环结束,内存增加96字节, 也是消耗在全局符号表上($val这个变量)。 循环结束之后,$val这个变量依然存在。

由此可见,foreach并没有导致zval:ht的复制。

再来看current():

print "INIT:".memory_get_usage().PHP_EOL;

$arr = range(1,2000);

print "FIRST ARRAY:".memory_get_usage().PHP_EOL;

$ref = $arr;

print "ref_count+1:".memory_get_usage().PHP_EOL;

var_dump(current($arr));

print "after current:".memory_get_usage().PHP_EOL;

输出:

INIT:625504

FIRST ARRAY:914200 //第一个数组创建出来之后,内存增加288696。

ref_count+1:914296 //$ref = $arr,ref_count+1 , 内存增加96字节。

int(1) //current()返回1, 正确。

after current:1106832 //current()之后,内存增加192536,可见发生了很多的复制...可是为什么会比288696要小呢? 相差96160。

current() 进行了一些复制,但是并没有把数组进行完全的复制,下面我就来猜测一下,究竟复制了数组的哪些部分。

1) 首先,zval ht肯定是被复制了。 zval占用48字节。

2) 其次,zval中的hash表:Bucket **arBuckets 被复制了,看一下Bucket的结构:

typedef struct bucket {

ulong h; // The hash (or for int keys the key) 8字节

uint nKeyLength; // The length of the key (for string keys) 4字节

void *pData; // The actual data 8字节

void *pDataPtr; // ??? What's this ??? 8字节

struct bucket *pListNext; // PHP arrays are ordered. This gives the next element in that order 8字节

struct bucket *pListLast; // and this gives the previous element 8字节

struct bucket *pNext; // The next element in this (doubly) linked list 8字节

struct bucket *pLast; // The previous element in this (doubly) linked list 8字节

const char *arKey; // The key (for string keys) 8字节

} Bucket;

共占用72字节,再加上16字节的mm信息,共占用88字节。 2000个数组元素就是 2000 * 88 = 176000字节。

考虑一下hash表的结构,还需要若干指针指向每一条链表的头。 假定每条链表中只有一个元素(这样hash表的查找效果最高,PHP应该能做到的),还需要2000个链表的头指针,共2000*8 = 16000字节。

176000 + 16000 = 192000,非常接近于192536字节了。

然后再反过来想一想,数组中的哪些部分没有被复制:

应该就是bucket中的 pData指向的zval没有被复制,即The actual data.

每个zval占用48字节, 2000个数组元素占用2000*48 = 96000字节,非常接近于96160。

感觉这样应该是正确的。

所以本文中曾经讲过的一句话:

凭直觉,arBuckets是不应该被复制的,因为它的值并未发生变化,被复制的应该只是ht,而且复制整个数组其实开销非常大,当然这只是我的直觉罢了。

应该纠正为:

凭直觉, ht被复制,arBuckets被复制,但是每个Bucket指向的zval没有被复制(这些zval的ref_count会加1)。

补充:

在current()时,zval分离,完成复制之后, 二个变量的ht的内部指针pInternalPointer被自动reset。

补充2:

通过在foreach循环内部插入 memory_get_usage(), 可以看到在foreach循环内部,内存增加192632字节,foreach结束之后,内存减少192536字节,说明在foreach内部还是发生了ht的复制,原作者的结论是正确的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值