php的foreach深度剖析

今天的操作中出现了如下的问题,下面试着解释其中原因:
先看看代码:
  1. $arr = array('a', 'b', 'c');
  2. foreach($arr as $key=>$item) {
  3.     $arr[$key] = $item . $item;//修改元素的值,只要是写操作即可,无论什么
  4. }
  5. var_dump($arr);
  6. var_dump(current($arr));
复制代码
上面的代码的结果为:
'aa', 'bb', 'cc'
'bb'
上面的第一行结果没有问题,主要是考虑下面的结果,为什么会是'bb'呢?

下面就开始解释这个问题:
影响这个结果的应该有以下的原因:
1,foreach所操作的是指定数组的一个拷贝
2,PHP所采取的是写时复制(copy on write, COW)
3,数组在复制时数组指针的处理规则

1,什么叫 foreach所操作的是指定数组的一个拷贝?
这句话的意思是指,foreach结构所指定的数组$arr,并不是我们在语句上看到的$arr, 内部处理时 是 处理的$arr 的一个拷贝,也就是 = $arr;
  1. $arr = array('a', 'b', 'c');
  2. foreach($arr as $value) 此处用来获得$value的,并不是语法上$arr 而是看不到的$arr的一个拷贝。
  3. 来个简单证明:
  4. foreach($arr as $item) {
  5. $arr[] = 'add';//每次循环增加一个$arr的元素
  6. echo 'once', ' ';//输出一个单词和空格
  7. }
  8. var_dump($arr);
复制代码
once once once array(6){'a','b', 'c', 'add', 'add', 'add'}
这个例子的结果是输出三个once 和 一个含义6个元素的数组。
说明我虽然对$arr增加了元素但是并没有在遍历时遍历到新增的元素,遍历时不是使用$arr这个元素,而是使用的$arr的一个拷贝-你看不到,我也看不到。

2什么是写时复制呢?
就是通过赋值的方式赋值给变量时,不会申请新的内存来存放新变量所保存的值,而是简单地通过一个计数器来公用内存,只有其中一个引用指向的变量的值发生变化时,才申请内存空间来保存新值,以达到节省内存空间的目的。例如:
$foo = 1;
$bar = $foo;
echo $bar + $foo; //2
上面的代码就不会开辟2个变量空间来保存相同的1 而是使用的是1个值空间。
如果接下来再执行:
$bar = 2;
此时$foo 与$bar的值发生了变化,需要为$bar开辟新的空间来保存这个值。
这个特性就是写时复制——在写数据的时候复制值空间。

再看例子:
  1. //未使用变量的内存
  2. var_dump(memory_get_usage());//327768
  3. $foo = str_repeat('itcast-php', 10000);
  4. //使用变量$foo之后的内存
  5. var_dump(memory_get_usage());//424864
  6. $bar = $foo;
  7. //将变量$foo 复制给$bar后的内存
  8. var_dump(memory_get_usage());//427912
  9. $bar = str_repeat('itcast-kang', 10000);
  10. //对$bar值修改后使用的内存
  11. var_dump(memory_get_usage());//537960
复制代码
从上面的例子可以看到 复制一个变量$bar后 内存的分配并没有像分配$ffoo时增加那么多的空间,而是在对$bar重新赋值时 才分配的额外的空间

以上特性就是 php为了优化内存而采用的 COW策略,很多语言中都有类似的优化,例如C++的STL中等

注意一点,如果在foreach时候使用的是引用传值,则不会出现拷贝,因而不会出现这个状况。


3,在数组之间复制是 数组指针是怎样的规律

例如$copy_arr = $arr;中
如果$arr的数组指针处于$arr数组内部(指向任意的一个元素),那么在赋值时,$arr的指针位置不变,而$copy_arr的指针与$arr的指针位置相同
如果$arr的数组指针处于数组末端(最后一个元素后,超出数组),那么在赋值时,$arr的指针会被初始化(指向第一个元素),而$copy_arr的指针位置还是处于数组的末端。
例如:
  1. $arr = array('a', 'b', 'c');

  2. next($arr);//移动一次指向第二个元素
  3. $copy_arr1 = $arr;
  4. var_dump(current($arr), current($copy_arr1));//b, b
  5. echo '
  6. ';
  7. next($arr);//再次移动 指向第三个元素
  8. $copy_arr2 = $arr;
  9. var_dump(current($arr), current($copy_arr2));//c c
  10. echo '
  11. ';
  12. next($arr);//再次移动 出去了
  13. $copy_arr3 = $arr;
  14. var_dump(current($arr), current($copy_arr3));//a false
复制代码
看以上的输出 体会指针赋值时的规律

好了 解释了 以上三个知识点后,下面开始试着解释最开始的问题了。
  1. $arr = array('a', 'b', 'c');
  2. foreach($arr as $key => $value) {
  3. $arr[$key] = $value . $value;
  4. }
  5. var_dump(current($arr));//'bb'
复制代码
原因如下:
当执行foreach这行语句时,应该使用$arr的副本来进行遍历(假设副本为$copy_arr),但由于是写时复制,直到第一次写操作时,$arr与$copy_arr才指向不同的值。当第一次执行到foreach 为$value 和$key 变量赋值数据时,没有发生写操作,因此不会产生新的值空间,此时就是操作的$arr数组,为$value和$key分配完毕值之后,会将数组指针向后移动一个单元,$arr的指针向后移动一个单元,指向第二个元素单元。接着马上执行了对$arr[$key]重新复制的操作,发生了写操作,写时复制,发生了复制。复制操作后$copy_arr与$arr是两个空间,因此遍历时使用$copy_arr 会移动$copy_arr 而不再会继续移动$arr。 $arr指针固定在第二个元素上。 所以输出current($arr)是获得的是第二个元素的值。

以下的例子加以说明:
  1. $arr = array('a', 'b', 'c');
  2. $i = 0;
  3. foreach($arr as $key => $value) {
  4.     if($i == 0) {
  5.         //第一次就执行了写操作
  6.         $arr[$key] = $value . $value;
  7.     }
  8.     $i++;
  9. }
  10. //$arr的指针被移动了一次,指向第二个元素b上。
  11. var_dump(current($arr));//b
复制代码
再看:
  1. $arr = array('a', 'b', 'c');
  2. $i = 0;
  3. foreach($arr as $key => $value) {
  4.     if($i == 1) {
  5.         //第二次执行了写操作
  6.         $arr[$key] = $value . $value;
  7.     }
  8.     $i++;
  9. }
  10. //$arr的指针被移动了二次,指向第三个元素c上。
  11. var_dump(current($arr));//c
复制代码
以上两个例子 说明 只要没有写操作,那么foreach使用的就是$arr,如果发生写操作将使用$copy_arr = $arr

最后一个例子:
  1. $arr = array('a', 'b', 'c');
  2. $i = 0;
  3. foreach($arr as $key => $value) {
  4.     if($i == 3) {
  5.         //第三次执行了写操作
  6.         $arr[$key] = $value . $value;
  7.     }
  8.     $i++;
  9. }
  10. //$arr的指针被移动了三次,移到的数组外部。
  11. //但是在$copy_arr = $arr时,按照数组指针复制规律,
  12. //由于$arr指针在数组末端,则会重置指针指向第一个元素 a
  13. var_dump(current($arr));//a
复制代码
注意第三个例子的注释部分,根据数组指针复制时的规律,输出的结果为第一个元素a
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值