资讯专栏INFORMATION COLUMN

再理解-PHP引用

ermaoL / 1339人阅读

摘要:引用本身概念好理解性能也很好但是用好它还是存在着一定的门槛不太好写。写本文的起因是这几天碰到非常好的一个解决方案,让我重新理解了引用。如果下面的代码,你看完就能理解了,说明你引用真是学到家了你也可以直接跳过本文哈。

起因:

日常开发中,我们会碰到构造树的需求,通过id,pid的关系去构建一个树结构,然后对树进行遍历等操作。其实现方式分为两种: 1. 递归, 2. 引用
而这两个方法的优缺点也很明显。

递归实现起来较容易,但是随着数着数据量的增大,其性能很低。

引用本身概念好理解,性能也很好,但是用好它还是存在着一定的门槛,不太好写。

写本文的起因是,这几天碰到非常好的一个解决方案,让我重新理解了引用。通过本文,总结下自己的学习成果.ok,那直接上代码了。

Practise

</>复制代码

  1. 如果下面的代码,你看完就能理解了,说明你引用真是学到家了, 你也可以直接跳过本文哈~。

</>复制代码

  1. function buildTreeByReference($data, $id = "id", $pid = "pid", $child = "children")
  2. {
  3. $tmp = []; //以id为健,$value为值的容器,可以很巧妙的判断根节点元素
  4. $tree = [];
  5. //利用引用,对$data的数据进行操作
  6. foreach ($data as $key => &$value) {
  7. //
  8. $tmp[$value["id"]] = &$value;
  9. if (!isset($tmp[$value["pid"]])) {
  10. $tree[] = &$tmp[$value["id"]];
  11. }else {
  12. $temp = &$tmp[$value["pid"]];
  13. $temp[$child][] = &$value;
  14. }
  15. unset($temp, $value);
  16. }
  17. return $tree;
  18. }

ok,先不说其他的,你先拿下面的数据测试下这个方法.

</>复制代码

  1. $data= [
  2. ["id" => 1, "pid" => 0 , "name" => "Universe"],
  3. ["id" => 2, "pid" => 1 , "name" => "Earth"],
  4. ["id" => 3, "pid" => 2 , "name" => "China"],
  5. ["id" => 4, "pid" => 3 , "name" => "Beijing"],
  6. ];

</>复制代码

  1. 补充:这个方法需要注意一点,需要父节点在前,不适合无序数据,所以如果是无序的,先得排序.

如果没有意外,打印的结果,应该如下:

</>复制代码

  1. array(1) {
  2. [0]=>
  3. array(4) {
  4. ["id"]=>
  5. int(1)
  6. ["pid"]=>
  7. int(0)
  8. ["name"]=>
  9. string(8) "Universe"
  10. ["children"]=>
  11. array(1) {
  12. [0]=>
  13. array(4) {
  14. ["id"]=>
  15. int(2)
  16. ["pid"]=>
  17. int(1)
  18. ["name"]=>
  19. string(5) "Earth"
  20. ["children"]=>
  21. array(1) {
  22. [0]=>
  23. array(4) {
  24. ["id"]=>
  25. int(3)
  26. ["pid"]=>
  27. int(2)
  28. ["name"]=>
  29. string(5) "China"
  30. ["children"]=>
  31. array(1) {
  32. [0]=>
  33. array(3) {
  34. ["id"]=>
  35. int(4)
  36. ["pid"]=>
  37. int(3)
  38. ["name"]=>
  39. string(7) "Beijing"
  40. }
  41. }
  42. }
  43. }
  44. }
  45. }
  46. }
  47. }

如果到此,你还想不明白,没关系,我们一一来分析下.
其实要彻底弄明白这个解决方案,需要理解二个部分。

foreach赋值原理

引用的原理

</>复制代码

  1. foreach

</>复制代码

  1. $data = ["student", "teacher"];
  2. foreach ($data as $index => $item) {
  3. }

注意每次循环的时候, 是把$data[0]和$data[1] 的“值”复制一份 再赋给 $item

</>复制代码

  1. 引用(一定要自己动手试验下)

</>复制代码

  1. $a = 1;
  2. $b = &$a;
  3. $c = $b;
  4. $c = 2;
  5. 猜猜看 $b = ?;

如果引用有疑问,点我

到此,如果你能理解上面foreach和引用,并且能理解这个解决方案的所有执行过程,那么恭喜你,你学的真好! 但如果还是有困难,没关系,咱们一步一步踏踏实实的来.

Analysis

ok,深吸一口气,跟着我的思路,咱们一步一步来.

</>复制代码

  1. 首先咱们看下原函数

</>复制代码

  1. function buildTreeByReference($data, $id = "id", $pid = "pid", $child = "children")
  2. {
  3. $tmp = []; #以id为健,$value为值的容器,可以很巧妙的判断根节点元素
  4. $tree = [];
  5. #利用引用,对$data的数据进行操作
  6. foreach ($data as $key => &$value) {
  7. #&$value取到$data元素对应值的引用
  8. $tmp[$value["id"]] = &$value;
  9. #以$value["id"]为键,&$value引用为值push到$tmp中,
  10. #这样可以巧妙的判断当前元素是否为根节点
  11. if (!isset($tmp[$value["pid"]])) {
  12. #将根节点push到$tree中
  13. $tree[] = &$tmp[$value["id"]];
  14. }else {
  15. #若当前元素的父节点存在于$tmp中, 引用获取$tmp中对应父节点的值
  16. $temp = &$tmp[$value["pid"]];
  17. #然后将当前元素push到其父节点的children中
  18. $temp[$child][] = &$value;
  19. }
  20. #为了不引起变量污染, 引用用完后,需要unset掉
  21. unset($temp, $value);
  22. }
  23. return $tree;
  24. }

第一次循环

</>复制代码

  1. function buildTreeByReference($data, $id = "id", $pid = "pid", $child = "children")
  2. {
  3. # $tmp = [];
  4. # $tree = [];
  5. # foreach ($data as $key => &$value) {
  6. //
  7. $tmp[$value["id"]] = &$value;
  8. if (!isset($tmp[$value["pid"]])) {
  9. $tree[] = &$tmp[$value["id"]];
  10. }else {
  11. # $temp = &$tmp[$value["pid"]];
  12. # $temp[$child][] = &$value;
  13. # }
  14. unset($temp, $value);
  15. }
  16. return $tree;
  17. }

变量情况:
$data[0] = ["id" => 1, "pid" => 0 , "name" => "Universe"];
$tmp[1] = &$data[0];
$tree[] = &$data[0]

第二次循环

</>复制代码

  1. function buildTreeByReference($data, $id = "id", $pid = "pid", $child = "children")
  2. {
  3. # $tmp = [];
  4. # $tree = [];
  5. # foreach ($data as $key => &$value) {
  6. //
  7. $tmp[$value["id"]] = &$value;
  8. # if (!isset($tmp[$value["pid"]])) {
  9. # $tree[] = &$tmp[$value["id"]];
  10. }else {
  11. $temp = &$tmp[$value["pid"]];
  12. $temp[$child][] = &$value;
  13. }
  14. unset($temp, $value);
  15. }
  16. return $tree;
  17. }

变量情况:
$data[1] = ["id" => 2, "pid" => 1 , "name" => "Earth"];
$value=&$data[1];
$tmp[2] = &$data[1];
注意:
$temp即&$tmp[1],即和$data[0]指向相同的地址
所以$temp["children"][] = &$value ,操作的结果是:

</>复制代码

  1. $data[
  2. [
  3. "id" => 1,
  4. "pid" => 0 ,
  5. "name" => "Universe"
  6. "children"=>[
  7. &$data[1], //注意:存储的是引用
  8. ]
  9. ]
  10. ...
  11. ]

4.第三次循环

</>复制代码

  1. function buildTreeByReference($data, $id = "id", $pid = "pid", $child = "children")
  2. {
  3. # $tmp = [];
  4. # $tree = [];
  5. # foreach ($data as $key => &$value) {
  6. //
  7. $tmp[$value["id"]] = &$value;
  8. # if (!isset($tmp[$value["pid"]])) {
  9. # $tree[] = &$tmp[$value["id"]];
  10. }else {
  11. $temp = &$tmp[$value["pid"]];
  12. $temp[$child][] = &$value;
  13. }
  14. unset($temp, $value);
  15. }
  16. return $tree;
  17. }

变量情况:
$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]的引用

</>复制代码

  1. $data[
  2. [
  3. "id" => 1,
  4. "pid" => 0 ,
  5. "name" => "Universe"
  6. "children"=>[
  7. &$data[1], //注意:存储的是引用
  8. ]
  9. ]
  10. ...
  11. ]

第三次循环的的时候,则是$data[1]["children"][] = &$value, 而$value指向的是$data[2]
,所以结果是:

</>复制代码

  1. $data[
  2. [
  3. "id" => 1,
  4. "pid" => 0 ,
  5. "name" => "Universe"
  6. "children"=>[
  7. // &$data[1], //注意:存储的是引用
  8. [
  9. "id" => 2,
  10. "pid" => 1 ,
  11. "name" => "Earth"
  12. "children" => [
  13. &data[2] //注意:存储的是引用
  14. ]
  15. ]
  16. ]
  17. ]
  18. ]
  19. ...
  20. ]

5.第四次循环

</>复制代码

  1. function buildTreeByReference($data, $id = "id", $pid = "pid", $child = "children")
  2. {
  3. # $tmp = [];
  4. # $tree = [];
  5. # foreach ($data as $key => &$value) {
  6. //
  7. $tmp[$value["id"]] = &$value;
  8. # if (!isset($tmp[$value["pid"]])) {
  9. # $tree[] = &$tmp[$value["id"]];
  10. }else {
  11. $temp = &$tmp[$value["pid"]];
  12. $temp[$child][] = &$value;
  13. }
  14. unset($temp, $value);
  15. }
  16. return $tree;
  17. }

变量情况:
$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]的引用

</>复制代码

  1. $data[
  2. [
  3. "id" => 1,
  4. "pid" => 0 ,
  5. "name" => "Universe"
  6. "children"=>[
  7. // &$data[1], //注意:存储的是引用
  8. [
  9. "id" => 2,
  10. "pid" => 1 ,
  11. "name" => "Earth"
  12. "children" => [
  13. &data[2] //注意:存储的是引用
  14. ]
  15. ]
  16. ]
  17. ]
  18. ]
  19. ...
  20. ]

第四次循环的的时候,则是$data[2]["children"][] = &$value, 而$value指向的是$data[3]
,所以结果是:

</>复制代码

  1. $data[
  2. [
  3. "id" => 1,
  4. "pid" => 0 ,
  5. "name" => "Universe"
  6. "children"=>[
  7. // &$data[1], //注意:存储的是引用
  8. [
  9. "id" => 2,
  10. "pid" => 1 ,
  11. "name" => "Earth"
  12. "children" => [
  13. // &data[2] //注意:存储的是引用
  14. [
  15. "id" => 3,
  16. "pid" => 2 ,
  17. "name" => "China"
  18. "children" =>[
  19. &$data[3]; //注意:存储的是引用
  20. ]
  21. ]
  22. ]
  23. ]
  24. ]
  25. ]
  26. ]
  27. ...
  28. ]

ok,至此,整个执行过程走通了,你懂了吗?:)

对了,还另外一个方法,也是通过引用的,这个我就不分析,要是理解上面的方法,下面的相对来说简单些。

</>复制代码

  1. public static function buildTreeByReference1($data, $id = "id", $pid = "pid", $child = "children")
  2. {
  3. $tmp = [];
  4. foreach ($data as $key => $value) {
  5. $tmp[$value[$id]] = $value;
  6. }
  7. $tree = [];
  8. foreach ($tmp as $key => $value) {
  9. if (isset($tmp[$value["pid"]])) {
  10. $tmp[$value["pid"]]["children"][] = &$tmp[$key];
  11. }else{
  12. $tree[] = &$tmp[$key];
  13. }
  14. }
  15. return $tree;
  16. }

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/28600.html

相关文章

  • PHP引用传递+unset+global理解,希望大神指正

    摘要:即产生了相当于这样的效果,所以改变的值也同时改变了的值。不要用返回引用来增加性能,引擎足够聪明来自己进行优化。只能从函数返回引用变量没别的方法。 关键是对global的误解,之前以为在函数中global变量,就是把函数外部的变量拿进函数内部使用,但似乎我错了引用传递+unset+global理解 php的引用(就是在变量、函数、对象等前面加上&符号)在PHP中引用的意思是:不同的名字访...

    ConardLi 评论0 收藏0
  • PHP 引用是个坑,请慎用

    摘要:发布时最大的变动是对象处理方式。这很容易被误解为引用,但是存储器的引用与引用是完全不同的概念。使用引用是一件不好的事情,除了引用本身不好,并且还会使性能下降这个事实外,使用引用这种方式会使得代码难以维护。 showImg(https://segmentfault.com/img/remote/1460000014082570); 去年我参加了很多次会议,其中八次会议里我进行了相关发言,...

    dockerclub 评论0 收藏0
  • 深入理解PHP7之zval

    摘要:已经发布如承诺我也要开始这个系列的文章的编写今天我想先和大家聊聊的变化在讲变化的之前我们先来看看在下面是什么样子回顾在的时候的定义如下对内核有了解的同学应该对这个结构比较熟悉因为可以表示一切中的数据类型所以它包含了一个字段表示这个存储的是什 PHP7已经发布, 如承诺, 我也要开始这个系列的文章的编写, 今天我想先和大家聊聊zval的变化. 在讲zval变化的之前我们先来看看zval在...

    Yuanf 评论0 收藏0
  • [译] PHP 的变量实现(给PHP开发者的PHP源码-第三部分)

    摘要:文章来自原文在给开发者的源码系列的第三篇文章,我们打算扩展上一篇文章来帮助理解内部是怎么工作的。进入在的核心代码中,变量被称为。要转换一个为值,就调用函数。有了这个东西,我们可以看到函数马上调用函数。 文章来自:http://www.hoohack.me/2016/02/12/phps-source-code-for-php-developers-part3-variables-ch...

    Imfan 评论0 收藏0
  • 单例模式的理解php

    摘要:单例模式顾名思义,就是只有一个实例。为什么要使用单例模式语言本身的局限性语言是一种解释型的脚本语言,这种运行机制使得每个页面被解释执行后,所有的相关资源都会被回收。 单例模式(Singleton Pattern):顾名思义,就是只有一个实例。作为对象的创建模式,单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。 为什么要使用单例模式 1、PHP语言本身的局限性P...

    Invoker 评论0 收藏0

发表评论

0条评论

ermaoL

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<