有没有一种函数可以将一个PHP数组复制到另一个数组中?

644

有没有一种方法可以将一个PHP数组复制到另一个数组中?

在尝试复制PHP数组时,我已经遇到了几次问题。我想把一个定义在对象内部的数组复制到对象外部的全局变量中。


虽然有点晚了,但在我的环境中我测试过这个代码(它是有效的): function arrayCopy(array $a) { return $a; } $a1 = array(); for ($i=0; $i<3; $i++) { $a1["key-$i"] = "value #$i"; } $a1["key-sub-array"] = array(1, 2, 3, 4); $a2 = $a1; $a3 = arrayCopy($a1); for ($i=0; $i<3; $i++) { if (!is_array($a2["key-$i"])) { $a2["key-$i"] = "changed value #$i"; } } $a2["key-sub-array"] = array("changed sub-array 1", "changed sub-array 2"); var_dump($a1); var_dump($a2); var_dump($a3);关键是不要将数组作为引用传递到函数中。;-) - Sven
2
@Sven 这是一条评论而不是答案,有什么原因吗?我看不懂它的意思。 - ggorlen
19个回答

1090

在PHP中,除了对象以外的所有变量都是通过所谓的写时复制机制来分配的,而对象则是通过引用来分配的。这意味着对于仅包含标量值的数组,简单地使用$b = $a就可以获得一个副本:

$a = array();
$b = $a;
$b['foo'] = 42;
var_dump($a);

将产生:

array(0) {
}

与对象相关,

$a = new StdClass();
$b = $a;
$b->foo = 42;
var_dump($a);

产生:

object(stdClass)#1 (1) {
  ["foo"]=>
  int(42)
}

当数组元素可能是需要克隆的对象时,会出现一个极端情况,详见另一个答案

您可能会被复杂的细节所困扰,例如 ArrayObject,它是一个完全像数组一样运作的对象。然而,由于它是一个对象,它具有引用语义。

编辑:@AndrewLarsson 在下面的评论中提出了一个观点。PHP 有一个称为 "引用" 的特殊功能。它们在某种程度上类似于 C/C++ 中的指针,但并不完全相同。如果您的数组包含引用,则虽然数组本身是按副本传递的,但引用仍将解析为原始目标。当然,这通常是期望的行为,但我认为值得一提。


145
你没有回答问题,只是解释了问题所在,而对于提问者来说,这很可能是他正在寻找的。然而,对于我(以及其他人),在将近四年之后遇到相似的问题时,我仍然没有一个好的方法可以复制一个数组而不修改原始数组(包括内部指针)。我想现在是时候提出我的问题了。 - Andrew Larsson
40
@AndrewLarsson,但是PHP默认会这样做-这就是要点。不过,引用没有被解析,因此如果需要,您将不得不递归遍历数组并构建一个新数组。同样,如果源数组包含对象,并且您想要复制它们,那么您将不得不手动进行操作。还要注意,在PHP中,引用等同于C语言中的指针。在不知道您的具体情况的情况下,我可以建议在第一种情况下拥有一个引用数组似乎很奇怪,特别是如果您不打算将它们视为引用。这是什么用例? - troelskn
2
@troelskn 我在这个问题下添加了一个答案,解决了我的问题:https://dev59.com/DXI_5IYBdhLWcg3wFu_L#17729234 - Andrew Larsson
3
但是如果这不是期望的行为呢?问题要求如何进行深度复制,显然这不是期望的行为。你的答案并没有比“$copy = $original;”更好。此方法在数组元素是引用时无法正常工作。 - doug65536
11
像往常一样,php 给我们带来了最意想不到的结果。因为这个解决方案并不总是有效。$a=array(); $b=$a; $b["x"]=0; $c=$b; $b["x"]=1; echo gettype($b), $c["x"];打印出 array0,而 $a=$GLOBALS; $b=$a; $b["x"]=0; $c=$b; $b["x"]=1; echo gettype($b), $c["x"]; 则打印出 array1。显然有些数组是通过引用复制的。 - Tino
显示剩余8条评论

253

PHP默认会复制数组。在PHP中,引用必须是显式的。

$a = array(1,2);
$b = $a; // $b will be a different array
$c = &$a; // $c will be a reference to $a

如果数组很大,使用引用可能很重要。我不确定,但我认为它应该会导致更少的内存消耗和更好的性能(无需在内存中复制整个数组)。 - robsch
26
在程序逻辑层面,数组会被复制。但在内存中,只有当它被修改时才会实际复制,因为PHP对所有类型都使用写时复制语义。https://dev59.com/lGgu5IYBdhLWcg3wy558 - Jessica Knight
@MightyPork 你是什么意思?我已经尝试过这个了$b = $a; $b[0][1] = 'c'; var_dump($a);但$a没有改变。 - Thịnh Phạm
@ThịnhPhạm 我不知道,那是3年前的事了。 - MightyPork
@ThịnhPhạm 我认为他的意思是他所写的,包括“写时复制”。为了澄清,除非您实际更改 $b 元素,否则您的代码实际上不会将 $a 复制到 $b。在那之前,b 实际上引用 a。(我意识到在这个例子中这是一个非常短的时间。)所以是的,你们两个都可以正确。 - Dennis

57
如果你有一个包含对象的数组,需要复制该数组但不触动其内部指针,并且需要克隆所有对象(这样在对复制的数组进行更改时就不会修改原始对象),请使用此方法。
不触动数组的内部指针的技巧是确保你正在使用数组的副本,而不是原始数组(或其引用),因此使用函数参数可以完成任务(因此,这是一个接受数组的函数)。
请注意,如果你希望克隆对象的属性,则仍需要在对象上实现__clone()
此函数适用于任何类型的数组(包括混合类型)。
function array_clone($array) {
    return array_map(function($element) {
        return ((is_array($element))
            ? array_clone($element)
            : ((is_object($element))
                ? clone $element
                : $element
            )
        );
    }, $array);
}

1
请注意,这是一个有点特殊的情况。另外,请注意,这只会克隆第一级引用。如果您有一个深层次的数组,并且它们是引用,则无法克隆更深层次的节点。也许在您的情况下不是问题,但请记住这一点。 - troelskn
5
@troelskn,我通过添加一些递归修复了它。这个函数现在可以处理任何类型的数组,包括混合类型。它同样适用于简单的数组,因此不再是局限性的。它基本上是一个通用的数组克隆机。如果对象是深层的,则仍需要定义__clone()函数,但这超出了此函数的“范围”(对于糟糕的双关语,我感到抱歉)。 - Andrew Larsson
2
我坚信这是这个问题的实际答案,我所见过的唯一深度复制包含对象数组的方法。 - Patrick
1
@ya.teck 正确,这就是为什么开发人员需要实现__clone()函数的原因(因为只有开发人员知道需要克隆什么)。如果A存储了B,则需要在A上实现__clone()函数。并且,在A__clone()函数内部,您需要确保克隆B。以下是一个示例,说明如何以及为什么需要:http://sandbox.onlinephpfunctions.com/code/ebac24f320a458f0be660df4e80e79b9dd488a80 - Andrew Larsson
1
@John 是的,当在匿名函数中使用__FUNCTION__时,它们改变了其行为。我会更新我的答案,只使用普通递归。 - Andrew Larsson
显示剩余10条评论

34

当你执行

$array_x = $array_y;

PHP会复制数组,因此我不确定您如何受到影响。对于您的情况,

global $foo;
$foo = $obj->bar;

应该可以正常工作。

要被烧毁,我认为你必须使用引用或期望数组中的对象被克隆。


13
对于这个+1:"或者希望在数组中的对象被克隆"。 - Melsi

29

简单且深度复制,打破所有链接

$new=unserialize(serialize($old));

10
通常情况下它运行良好,但在某些情况下,可能会抛出异常,因为并非所有变量都可以序列化(例如闭包和数据库连接)。 - ya.teck
另一个需要注意的是,如果一个类实现了__wakeup魔术方法,对象引用可以被恢复。 - ya.teck
谢谢,终于有真正有效的答案了,而不是其他那些被点赞很多但并没有处理对象数组的问题,其中数组元素数量可能会改变,但对象引用肯定不会改变。 - FantomX1

29

我喜欢使用 array_replace(或 array_replace_recursive)。

$cloned = array_replace([], $YOUR_ARRAY);

它的作用类似于 JavaScript 中的 Object.assign

$original = [ 'foo' => 'bar', 'fiz' => 'baz' ];

$cloned = array_replace([], $original);
$clonedWithReassignment = array_replace([], $original, ['foo' => 'changed']);
$clonedWithNewValues = array_replace([], $original, ['add' => 'new']);

$original['new'] = 'val';

会导致

// original: 
{"foo":"bar","fiz":"baz","new":"val"}
// cloned:   
{"foo":"bar","fiz":"baz"}
// cloned with reassignment:
{"foo":"changed","fiz":"baz"}
// cloned with new values:
{"foo":"bar","fiz":"baz","add":"new"}

3
那么array_slice($arr, 0)呢?或者当您不关心键时,array_values($arr)呢?我认为它们可能比在数组中搜索更快。此外,在JavaScript中,使用Array.slice()克隆数组非常流行。 - Christian
在JS中,我们有Object用于键值对和Array。PHP没有这种区别。对于带有编号索引的PHP数组,array_slice和所有其他在此处提到的方法都非常有效。但是,如果您想合并多个键值对(就像通过Object.assignspread-syntax在JS-Objects中也可以实现),则array_replace可能更有用。 - Putzi San
1
@Christian 谢谢你提供 array_values() 的建议,它完美地解决了我的问题。 - bigsee

25

array_merge()是PHP中一个可将一个数组复制到另一个数组的函数。


6
是的,但具有数字键的输入数组中的值将在结果数组中以从零开始的递增键重新编号。 - zamnuts
1
@zamnuts 为了维护键值,可以使用$a_c = array_combine(array_keys($a), array_values($a)) - CPHPython

13

如果您的数组中只有基本类型,您可以这样做:

$copy = json_decode( json_encode($array), true);

您不需要手动更新参考文献
我知道这种方法并不适用于每个人,但对我有用


8
+1 这是一件真的很糟糕的事情,但从技术上来说它是正确而聪明的。如果我在代码中看到这个,我会摇头叹息,但我克制不住对它的喜爱。 - Reactgular
@Reactgular 你没有解释你的“观点”,在你解释之前,没有人应该把你的评论视为有用的。相反,编码和解码有许多有用的目的,这似乎超出了你对数据管理的理解。 - undefined

9

我知道这个方法很早以前就存在了,但它对我有效...

$copied_array = array_slice($original_array,0,count($original_array));

6
不需要计数:$copied_array = array_slice($original_array, 0);就足够了。 - ToolmakerSteve

8
我发现最安全、最便宜的方法是:
<?php 
$b = array_values($a);

这也有重新索引数组的好处。
对于关联数组(哈希表),这不会按预期工作,但大多数先前的答案也不会。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接