谈一谈PHP变量或参数的Copy On Write机制

php的copy on wirte(写时复制) 特性,是为了节约内存而使用的一种特性。Copy on Write其实就是变量的值发生改变时,会检查变量指向的zval的ref_count, 如果ref_count 大于1时,就会发生分离,即变量会真正分配自己独立的内存空间。如果变量没有发生写,比如一个大数组作为参数传递给一个方法时,且大数组在方法内只是读,没有写,这种情况其实还是原来的内存,没有新分配,这就是copy on write的意义所在。

在php中所有的数据类型在赋值或者传参过程中都有copy on wirte机制。

在php5以后,对象默认按 “隐式的引用传递”,其他数据类型默认按值传递(但是依旧有copy on wirte机制),为何说对象默认是隐式的引用传递呢。因为只有用&符号声明的是显示的引用传递,其指向的zval的is_ref=true, 而隐式的引用传递is_ref=false,其实之所以说是“隐式的引用传递”还有一个原因是:当对象在方法内部重新赋值时会触发copy on write,修改对象的属性$obj->attr= val 这种方式不会触发copy on write,$obj = new_val 这种会触发copy on write,而&引用传参,永远都是指向同一个内存空间,无论怎么修改,都无法触发copy on write

下面用代码+注解的方式 简要描述一下各数据类型的变量copy on wirte触发时机

 

<?php
// /// string类型分析 //
$str = "hello";
$str2 = $str; //此处$str和str2的值存放在同一份内存空间
$str2 = "hi"; /** 此时触发copy on write **/

// /// integer类型分析
$age = 25;
$number = $age;
$age = 55; /** 此时触发copy on write */

/// /// object类型分析 /

$obj = new stdClass();
$obj->name = "aaa";

$obj2 = $obj; //此时两个对象指向同一份内存空间
$obj2->height = 50; //修改对象的属性,不会触发copy on write,即:obj === $obj2
//通过print_r打印obj可以发现,修改obj2的属性,obj也有该属性了,说明他们指向同一个内存空间
print_r($obj);

//那么问题来了,对象什么时候会发生copy on write呢? 答案就是其中一个变量重新new的时候。
$obj2 = new stdClass();
$obj2->avatar = "a.png";
//通过下面的打印,可以分析出来,obj和obj2各自的属性数据已经完全独立了,说明发生了copy on write
print_r($obj);
print_r($obj2);

// 打印结果为
/**
 stdClass Object
(
    [name] => aaa
    [height] => 50
)

stdClass Object
(
    [avatar] => a.png
)

**/


// 如果把 17行的代码改为 $obj2 = &$obj; 那么obj和obj2就永远不会发生copy on write了(即:它们的值永远相等)。这才是真正的 "&引用传递"

// /接下来研究一下数组作为方法参数传递时,是否有copy on wirte机制

function processArr(array $arr)
{
    $arr1 = ["hello"];
    $arr = array_merge($arr, $arr1); //此时发生copy on write,此时$arr已经分离了一个副本出来和外部的$arr已经保持独立了,这个$arr类似局部变量了
    print_r(count($arr));  //输出 100000
    var_dump(memory_get_usage());  //输出int(8753976),内存翻倍了,说明发生copy on write,如果$arr只读不写,则不会分配新的内存空间。
    echo PHP_EOL;
}

var_dump(memory_get_usage());  //输出int(357080)
$arr = range(1, 99999);
var_dump(memory_get_usage());  //输出int(4555560),此处开始为数组分配内存4M左右

processArr($arr); // 数组传递到方法内,如果没有copy on write,会发生大内存拷贝
print_r(count($arr)); //输出99999

var_dump(memory_get_usage()); //输出int(4555560),方法调用完成,方法内分配的内存释放掉了


// 下面看看对象传递的copy on write特性,在php5以后,对象默认按 "隐式的引用传递"

class Demo
{
    public $val;
}

function processObj(Demo $demo)
{
    $demo->val = 666;
    $demo = new stdClass(); //此时触发copy on write,$demo变成临时变量,和外部的$demo发生了分离
    $demo->val = "stdClass_val_8";
}

$demo = new Demo();
processObj($demo);
var_dump($demo->val); //打印666,也就是在processObj方法内,在触发copy on write之前的代码,都是操作外部的$demo本身

 

通过上面的代码实例,可以总结出来:

在作为方法参数传递时,默认的情况下:

1、数组$arr[]=append_item, $arr[0]=new_val, $arr=new_arr 这三种数组修改方法都会触发copy on write

2、对象$obj->attr = val 不会触发copy on write,而$obj = new_val 会触发copy on write。(这点是比较容易混淆的点,也正是因为$obj->attr=val方式不触发copy on write,才让对象传参表现上有点像引用&)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值