最近在重构一个老项目,遇到了一个看似简单却暗藏玄机的问题:如何优雅地往PHP数组里追加元素。这让我想起了当年刚学PHP时,只会用array_push的日子,现在回头看简直太naive了。今天就来聊聊PHP数组追加的各种姿势,顺便分享几个实战中踩过的坑。
先来看最基础的array_push用法:
$fruits = ['apple', 'banana'];
array_push($fruits, 'orange');
// 现在$fruits是['apple', 'banana', 'orange']
看起来很简单对?但这里有个性能陷阱。array_push在处理单个元素时,其实比直接使用[]语法要慢。因为array_push是个函数调用,而[]是语言结构。当你在循环里大量使用时,这个差异就会很明显。
说到[]语法,这是我最喜欢的追加方式:
$fruits[] = 'pear';
// 简单粗暴有效
但有时候你会遇到这样的情况:
$fruits = null;
$fruits[] = 'apple'; // 报错:Warning: Cannot use a scalar value as an array
这时候就需要先初始化数组:
$fruits = [];
$fruits[] = 'apple'; // 这下舒服了
在实战中,我经常需要合并数组。array_merge是个不错的选择:
$arr1 = ['a', 'b'];
$arr2 = ['c', 'd'];
$result = array_merge($arr1, $arr2);
// $result是['a', 'b', 'c', 'd']
但要注意,array_merge会重新索引数字键。如果你想保留数字键,可以用+运算符:
$arr1 = [0 => 'a', 1 => 'b'];
$result = $arr1 + $arr2;
// $result是[0 => 'a', 1 => 'b']
因为+运算符遇到相同键名时会保留第一个数组的值。这个特性在处理配置合并时特别有用。
说到配置合并,不得不提array_replace。它和array_merge的区别在于,array_replace会保留键名,包括数字键:
$defaults = ['color' => 'red', 'size' => 'M'];
$userPref = ['color' => 'blue'];
$result = array_replace($defaults, $userPref);
// $result是['color' => 'blue', 'size' => 'M']
在处理多维数组时,你可能需要array_merge_recursive:
$arr1 = ['fruits' => ['apple', 'banana']];
$arr2 = ['fruits' => ['orange']];
$result = array_merge_recursive($arr1, $arr2);
// $result是['fruits' => ['apple', 'banana', 'orange']]
但要注意,如果键名相同但值不是数组,它会把这些值合并到一个数组里:
$arr1 = ['count' => 1];
// $result是['count' => [1, 2]]
这有时候不是你想要的。这时候可以考虑自己写递归合并函数。
说到自定义函数,在处理大量数据追加时,性能很重要。我曾经在一个项目中需要处理百万级数据的追加,发现用[]比array_push快30%左右。测试代码如下:
$start = microtime(true);
$arr = [];
for ($i = 0; $i < 1000000; $i++) {
$arr[] = $i;
}
$end = microtime(true);
echo '[]用时:'.($end - $start).'秒';
array_push($arr, $i);
}
echo 'array_push用时:'.($end - $start).'秒';
在处理关联数组时,有个小技巧可以快速追加多个元素:
$user = ['name' => 'John'];
$user += ['age' => 30, 'gender' => 'male'];
// $user现在是['name' => 'John', 'age' => 30, 'gender' => 'male']
这比多次使用[]或者array_merge要简洁得多。
有时候我们需要在数组开头追加元素,这时候array_unshift就派上用场了:
$queue = ['b', 'c'];
array_unshift($queue, 'a');
// $queue现在是['a', 'b', 'c']
但要注意,array_unshift会重新索引数字键,这在处理某些数据结构时可能会带来问题。
在PHP7.4之后,我们可以使用展开运算符(...)来追加数组:
$arr1 = [1, 2];
$arr2 = [3, 4];
$result = [...$arr1, ...$arr2];
// $result是[1, 2, 3, 4]
这个语法糖在处理参数传递时特别有用。
说到参数传递,在处理可变参数函数时,func_get_args()返回的参数数组经常需要追加到其他数组中。这时候可以这样:
function foo(...$args) {
$defaults = ['a', 'b'];
return [...$defaults, ...$args];
}
foo('c', 'd'); // 返回['a', 'b', 'c', 'd']
在处理数据库结果时,我经常需要把查询结果追加到数组中。PDO的fetchAll可以直接返回数组,但有时候我们需要处理每一行:
$results = [];
while ($row = $stmt->fetch()) {
$results[] = $row;
}
这里有个性能优化点:如果知道结果集大小,可以先预分配数组大小:
$results = array_fill(0, $estimatedCount, null);
foreach ($results as &$row) {
$row = $stmt->fetch();
}
这样可以避免PHP频繁调整数组大小带来的性能开销。
在处理JSON API时,经常需要构建嵌套数组。这时候要注意引用的问题:
$data = ['items' => []];
$items = &$data['items'];
$items[] = ['id' => 1];
// $data现在是['items' => [['id' => 1]]]
使用引用可以避免多次写$data['items'],但要小心不要在不需要的地方意外创建引用。
最后分享一个真实案例:有一次我需要处理一个超大CSV文件,每行都要追加到数组中。直接使用[]导致内存爆炸,因为PHP数组的内存使用效率不高。解决方案是使用SplFixedArray:
$data = new SplFixedArray($lineCount);
foreach ($data as $i => &$row) {
$row = fgetcsv($handle);
}
SplFixedArray比普通数组更节省内存,特别是处理大量数值数据时。
总结一下PHP数组追加的各种姿势:
简单追加:$arr[] = $value
批量追加:array_merge, +, ...
开头追加:array_unshift
性能优化:预分配大小,SplFixedArray
特殊需求:array_replace, array_merge_recursive
记住,没有最好的方法,只有最适合当前场景的方法。选择时要考虑:是否需要保留键名、性能要求、代码可读性等因素。