再理解PHP引用
起因:
日常开发中,我们会碰到构造树的需求,通过id,pid的关系去构建一个树结构,然后对树进行遍历等操作。其实现方式分为两种: 1. 递归, 2. 引用
而这两个方法的优缺点也很明显。
递归实现起来较容易,但是随着数着数据量的增大,其性能很低。
引用本身概念好理解,性能也很好,但是用好它还是存在着一定的门槛,不太好写。
写本文的起因是,这几天碰到非常好的一个解决方案,让我重新理解了引用。通过本文,总结下自己的学习成果.ok,那直接上代码了。
Practise
如果下面的代码,你看完就能理解了,说明你引用真是学到家了, 你也可以直接跳过本文哈~。
function buildTreeByReference($data, $id = 'id', $pid = 'pid', $child = "children") { $tmp = []; //以id为健,$value为值的容器,可以很巧妙的判断根节点元素 $tree = []; //利用引用,对$data的数据进行操作 foreach ($data as $key => &$value) { // $tmp[$value['id']] = &$value; if (!isset($tmp[$value['pid']])) { $tree[] = &$tmp[$value['id']]; }else { $temp = &$tmp[$value['pid']]; $temp[$child][] = &$value; } unset($temp, $value); } return $tree; }
ok,先不说其他的,你先拿下面的数据测试下这个方法.
$data= [ ["id" => 1, "pid" => 0 , "name" => 'Universe'], ["id" => 2, "pid" => 1 , "name" => 'Earth'], ["id" => 3, "pid" => 2 , "name" => 'China'], ["id" => 4, "pid" => 3 , "name" => 'Beijing'], ];
补充:这个方法需要注意一点,需要父节点在前,不适合无序数据,所以如果是无序的,先得排序.
如果没有意外,打印的结果,应该如下:
array(1) { [0]=> array(4) { ["id"]=> int(1) ["pid"]=> int(0) ["name"]=> string(8) "Universe" ["children"]=> array(1) { [0]=> array(4) { ["id"]=> int(2) ["pid"]=> int(1) ["name"]=> string(5) "Earth" ["children"]=> array(1) { [0]=> array(4) { ["id"]=> int(3) ["pid"]=> int(2) ["name"]=> string(5) "China" ["children"]=> array(1) { [0]=> array(3) { ["id"]=> int(4) ["pid"]=> int(3) ["name"]=> string(7) "Beijing" } } } } } } } }
如果到此,你还想不明白,没关系,我们一一来分析下.
其实要彻底弄明白这个解决方案,需要理解二个部分。
foreach赋值原理
引用的原理
foreach
$data = ["student", "teacher"]; foreach ($data as $index => $item) { }
注意每次循环的时候, 是把$data[0]和$data[1] 的“值”复制一份 再赋给 $item
引用(一定要自己动手试验下)
$a = 1; $b = &$a; $c = $b; $c = 2; 猜猜看 $b = ?;
如果引用有疑问,点我
到此,如果你能理解上面foreach和引用,并且能理解这个解决方案的所有执行过程,那么恭喜你,你学的真好! 但如果还是有困难,没关系,咱们一步一步踏踏实实的来.
Analysis
ok,深吸一口气,跟着我的思路,咱们一步一步来.
首先咱们看下原函数
function buildTreeByReference($data, $id = 'id', $pid = 'pid', $child = "children") { $tmp = []; #以id为健,$value为值的容器,可以很巧妙的判断根节点元素 $tree = []; #利用引用,对$data的数据进行操作 foreach ($data as $key => &$value) { #&$value取到$data元素对应值的引用 $tmp[$value['id']] = &$value; #以$value['id']为键,&$value引用为值push到$tmp中, #这样可以巧妙的判断当前元素是否为根节点 if (!isset($tmp[$value['pid']])) { #将根节点push到$tree中 $tree[] = &$tmp[$value['id']]; }else { #若当前元素的父节点存在于$tmp中, 引用获取$tmp中对应父节点的值 $temp = &$tmp[$value['pid']]; #然后将当前元素push到其父节点的children中 $temp[$child][] = &$value; } #为了不引起变量污染, 引用用完后,需要unset掉 unset($temp, $value); } return $tree; }
第一次循环
function buildTreeByReference($data, $id = 'id', $pid = 'pid', $child = "children") { # $tmp = []; # $tree = []; # foreach ($data as $key => &$value) { // $tmp[$value['id']] = &$value; if (!isset($tmp[$value['pid']])) { $tree[] = &$tmp[$value['id']]; }else { # $temp = &$tmp[$value['pid']]; # $temp[$child][] = &$value; # } unset($temp, $value); } return $tree; }
变量情况:
$data[0] = ["id" => 1, "pid" => 0 , "name" => 'Universe'];
$tmp[1] = &$data[0];
$tree[] = &$data[0]
第二次循环
function buildTreeByReference($data, $id = 'id', $pid = 'pid', $child = "children") { # $tmp = []; # $tree = []; # foreach ($data as $key => &$value) { // $tmp[$value['id']] = &$value; # if (!isset($tmp[$value['pid']])) { # $tree[] = &$tmp[$value['id']]; }else { $temp = &$tmp[$value['pid']]; $temp[$child][] = &$value; } unset($temp, $value); } return $tree; }
变量情况:
$data[1] = ["id" => 2, "pid" => 1 , "name" => 'Earth'];
$value=&$data[1];
$tmp[2] = &$data[1];
注意:
$temp即&$tmp[1],即和$data[0]指向相同的地址
所以$temp['children'][] = &$value ,操作的结果是:
$data[ [ "id" => 1, "pid" => 0 , "name" => 'Universe' "children"=>[ &$data[1], //注意:存储的是引用 ] ] ... ]
4.第三次循环
function buildTreeByReference($data, $id = 'id', $pid = 'pid', $child = "children") { # $tmp = []; # $tree = []; # foreach ($data as $key => &$value) { // $tmp[$value['id']] = &$value; # if (!isset($tmp[$value['pid']])) { # $tree[] = &$tmp[$value['id']]; }else { $temp = &$tmp[$value['pid']]; $temp[$child][] = &$value; } unset($temp, $value); } return $tree; }
变量情况:
$data[2] = ["id" => 3, "pid" => 2 , "name" => 'China'];
$value = &$data[2];
$tmp[3] = &$data[2];
注意:
$temp即&$tmp[2],即和$data[1]指向相同的地址
所以$temp['children'][] = &$value ,操作的结果是:
这里注意一下:
这是第二次循环的时候,children中存储的$data[1]的引用
$data[ [ "id" => 1, "pid" => 0 , "name" => 'Universe' "children"=>[ &$data[1], //注意:存储的是引用 ] ] ... ]
第三次循环的的时候,则是$data[1]['children'][] = &$value, 而$value指向的是$data[2]
,所以结果是:
$data[ [ "id" => 1, "pid" => 0 , "name" => 'Universe' "children"=>[ // &$data[1], //注意:存储的是引用 [ "id" => 2, "pid" => 1 , "name" => 'Earth' "children" => [ &data[2] //注意:存储的是引用 ] ] ] ] ] ... ]
5.第四次循环
function buildTreeByReference($data, $id = 'id', $pid = 'pid', $child = "children") { # $tmp = []; # $tree = []; # foreach ($data as $key => &$value) { // $tmp[$value['id']] = &$value; # if (!isset($tmp[$value['pid']])) { # $tree[] = &$tmp[$value['id']]; }else { $temp = &$tmp[$value['pid']]; $temp[$child][] = &$value; } unset($temp, $value); } return $tree; }
变量情况:
$data[3] = ["id" => 4, "pid" => 3 , "name" => 'Beijing'];
$value = &$data[3];
$tmp[3] = &$data[3];
注意:
$temp即&$tmp[2],即和$data[1]指向相同的地址
所以$temp['children'][] = &$value ,操作的结果是:
这里注意一下:
这是第三次循环的时候,children中存储的$data[2]的引用
$data[ [ "id" => 1, "pid" => 0 , "name" => 'Universe' "children"=>[ // &$data[1], //注意:存储的是引用 [ "id" => 2, "pid" => 1 , "name" => 'Earth' "children" => [ &data[2] //注意:存储的是引用 ] ] ] ] ] ... ]
第四次循环的的时候,则是$data[2]['children'][] = &$value, 而$value指向的是$data[3]
,所以结果是:
$data[ [ "id" => 1, "pid" => 0 , "name" => 'Universe' "children"=>[ // &$data[1], //注意:存储的是引用 [ "id" => 2, "pid" => 1 , "name" => 'Earth' "children" => [ // &data[2] //注意:存储的是引用 [ "id" => 3, "pid" => 2 , "name" => 'China' "children" =>[ &$data[3]; //注意:存储的是引用 ] ] ] ] ] ] ] ... ]
ok,至此,整个执行过程走通了,你懂了吗?:)
对了,还另外一个方法,也是通过引用的,这个我就不分析,要是理解上面的方法,下面的相对来说简单些。
public static function buildTreeByReference1($data, $id = 'id', $pid = 'pid', $child = "children") { $tmp = []; foreach ($data as $key => $value) { $tmp[$value[$id]] = $value; } $tree = []; foreach ($tmp as $key => $value) { if (isset($tmp[$value['pid']])) { $tmp[$value['pid']]['children'][] = &$tmp[$key]; }else{ $tree[] = &$tmp[$key]; } } return $tree; }
相关推荐:
以上就是再理解PHP引用的详细内容,更多请关注其它相关文章!