PHP 数组索引重排:`array_values` 等方法告别跳跃键名
2025-03-29 13:26:42
PHP 数组索引重排:告别跳跃的键名
处理 PHP 数组时,你可能会碰到这么个情况:用 unset
函数删除数组里的一些元素后,发现数组的键名(索引)变得不连续了。就像下面这样:
<?php
$myArray = [
1 => [ 'pid' => '915', 'size' => '2', 'count' => 11, 'fee' => '5500' ],
4 => [ 'pid' => '914', 'size' => '1', 'count' => 8, 'fee' => '1500' ],
6 => [ 'pid' => '913', 'size' => '2', 'count' => 5, 'fee' => '3200' ],
];
// 假设我们之前 unset 掉了索引 0, 2, 3, 5
// 现在打印 $myArray,会看到:
/*
array(3) {
[1]=>
array(4) {
["pid"]=> string(3) "915"
["size"]=> string(1) "2"
["count"]=> int(11)
["fee"]=> string(4) "5500"
}
[4]=>
array(4) {
["pid"]=> string(3) "914"
["size"]=> string(1) "1"
["count"]=> int(8)
["fee"]=> string(4) "1500"
}
[6]=>
array(4) {
["pid"]=> string(3) "913"
["size"]=> string(1) "2"
["count"]=> int(5)
["fee"]=> string(4) "3200"
}
}
*/
?>
这数组索引是 1、4、6,看着就别扭。要是想让它们变成整整齐齐的 0、1、2,该怎么办呢?
为什么会这样?unset
的行为分析
要搞清楚怎么解决,先得明白为什么 unset
会造成这个问题。
unset
在 PHP 里干的活儿很直接:就是把你指定的变量或者数组元素给“销毁”掉。对于数组元素来说,它会移除那个键(key)和对应的值(value)。
关键点在于:unset
只负责删除,它不会主动去重新整理数组剩下的元素的键名 。它觉得,既然你指定要删掉索引 2
的元素,那我就照做,至于索引 3
、4
还保持原样,这不是它的职责范围。所以,当你 unset
掉一个数字索引数组的某些元素后,剩下的元素索引自然就可能出现空缺,变得不连续了。
这跟数组的类型也有点关系。对于关联数组(用字符串做键名的),unset
掉一个元素通常不会引起“不连续”的困扰,因为我们本来就不期待它的键名是按数字顺序排的。但对于数字索引数组,我们往往希望它的索引是紧密排列的,这时候 unset
带来的空缺就很碍眼了。
解决方案:让索引听话
别担心,让这些跳跃的索引变得服服帖帖有好几种办法。下面介绍几种常用且有效的方案。
方案一:array_values()
- 简单直接
这是最常用,也往往是最推荐的方法,专门用来解决这个问题。
原理与作用
array_values()
函数的作用非常纯粹:它接收一个数组,然后返回一个只包含原数组所有值 的新数组。重点来了,这个新数组的键名会被重新建立 ,并且总是从 0 开始的连续数字索引(0, 1, 2, ...)。它根本不关心原来的键名是什么,一股脑儿把值取出来,排好队,贴上新标签。
代码示例
接续上面的例子,用 array_values()
处理一下:
<?php
$myArray = [
1 => [ 'pid' => '915', 'size' => '2', 'count' => 11, 'fee' => '5500' ],
4 => [ 'pid' => '914', 'size' => '1', 'count' => 8, 'fee' => '1500' ],
6 => [ 'pid' => '913', 'size' => '2', 'count' => 5, 'fee' => '3200' ],
];
// 使用 array_values() 获取值并重新索引
$reIndexedArray = array_values($myArray);
print_r($reIndexedArray);
/*
输出结果:
array(3) {
[0]=>
array(4) {
["pid"]=> string(3) "915"
["size"]=> string(1) "2"
["count"]=> int(11)
["fee"]=> string(4) "5500"
}
[1]=>
array(4) {
["pid"]=> string(3) "914"
["size"]=> string(1) "1"
["count"]=> int(8)
["fee"]=> string(4) "1500"
}
[2]=>
array(4) {
["pid"]=> string(3) "913"
["size"]=> string(1) "2"
["count"]=> int(5)
["fee"]=> string(4) "3200"
}
}
*/
?>
看!输出结果里的数组索引已经是整齐的 0, 1, 2 了。原来的键名 1, 4, 6 彻底不见了,达到了我们的目标。
优缺点
- 优点: 代码简洁,意图明确,性能通常很好。专门为提取值并创建数字索引数组而生。
- 缺点: 它创建了一个全新的数组。如果原数组非常大,可能会消耗更多内存。不过对于大多数场景,这不是问题。它完全丢弃了原始的键名,但在这个问题场景下,这正是我们想要的。
进阶使用技巧
array_values()
经常和 array_filter()
配合使用。当你用 array_filter()
根据某些条件筛选数组元素后,得到的数组键名也可能不连续。紧接着用 array_values()
就能快速获得一个索引连续的结果数组。
<?php
$scores = [0 => 59, 1 => 95, 2 => 45, 3 => 88, 4 => 70];
// 筛选出及格的分数 (>= 60)
$passedScores = array_filter($scores, function($score) {
return $score >= 60;
});
// print_r($passedScores); 此时输出可能是 [1 => 95, 3 => 88, 4 => 70],索引不连续
// 用 array_values() 重新索引
$reIndexedPassedScores = array_values($passedScores);
print_r($reIndexedPassedScores);
/*
输出结果:
array(3) {
[0]=> int(95)
[1]=> int(88)
[2]=> int(70)
}
*/
?>
方案二:手动循环构建新数组
如果你想更精细地控制过程,或者觉得 array_values()
不够“透明”,可以自己写个循环来创建新数组。
原理与作用
基本思路是:创建一个空数组,然后遍历原始数组(那个键名不连续的数组)。在循环里,把原数组的每个值 (注意,是值,不是键值对)添加到新数组中。当你用 []
语法向数组添加元素而不指定键名时,PHP 会自动为它分配下一个可用的连续数字索引,通常是从 0 开始。
代码示例
<?php
$myArray = [
1 => [ 'pid' => '915', 'size' => '2', 'count' => 11, 'fee' => '5500' ],
4 => [ 'pid' => '914', 'size' => '1', 'count' => 8, 'fee' => '1500' ],
6 => [ 'pid' => '913', 'size' => '2', 'count' => 5, 'fee' => '3200' ],
];
$reIndexedArray = []; // 初始化一个空数组
// 遍历原数组,只取值
foreach ($myArray as $value) {
$reIndexedArray[] = $value; // 将值追加到新数组,PHP自动分配 0, 1, 2... 索引
}
print_r($reIndexedArray);
// 输出结果和 array_values() 方法一样
/*
array(3) {
[0]=>
array(4) { ... }
[1]=>
array(4) { ... }
[2]=>
array(4) { ... }
}
*/
?>
这种方法同样能得到索引从 0 开始的连续数组。
优缺点
- 优点: 代码逻辑非常清晰,容易理解每一步在做什么。可以在循环内部加入额外的处理逻辑(比如修改值、根据条件决定是否添加等)。
- 缺点: 比起内建函数
array_values()
,代码稍微冗长一些。对于非常大的数组,纯 PHP 循环的性能可能略低于 C 语言实现的内建函数。
进阶技巧
这种手动循环的方式提供了极大的灵活性。假设你在重新索引的同时,还想给每个子数组添加一个表示原始索引的字段,就可以这样做:
<?php
$myArray = [
1 => [ 'pid' => '915', 'size' => '2', 'count' => 11, 'fee' => '5500' ],
4 => [ 'pid' => '914', 'size' => '1', 'count' => 8, 'fee' => '1500' ],
6 => [ 'pid' => '913', 'size' => '2', 'count' => 5, 'fee' => '3200' ],
];
$reIndexedArrayWithOriginKey = [];
foreach ($myArray as $originalKey => $value) {
$value['original_key'] = $originalKey; // 在子数组中添加一个字段记录原始键名
$reIndexedArrayWithOriginKey[] = $value; // 追加到新数组,获得新索引
}
print_r($reIndexedArrayWithOriginKey);
/*
输出结果:
array(3) {
[0]=>
array(5) {
["pid"]=> string(3) "915"
["size"]=> string(1) "2"
["count"]=> int(11)
["fee"]=> string(4) "5500"
["original_key"]=> int(1) // 看这里!
}
[1]=>
array(5) {
["pid"]=> string(3) "914"
// ...
["original_key"]=> int(4) // 看这里!
}
[2]=>
array(5) {
["pid"]=> string(3) "913"
// ...
["original_key"]=> int(6) // 看这里!
}
}
*/
?>
这就体现了手动循环的灵活性,array_values()
可没法直接这么干。
方案三:array_splice()
的“间接”应用 (特定场景)
array_splice()
主要功能是在数组中移除、替换或插入元素,并且它有一个副作用:它会重新索引数组中受影响部分之后的元素 。我们可以利用这个特性,但通常不是解决这个特定问题的首选,因为它有点绕。
原理与作用
你可以用 array_splice()
“移除”数组的 0 个元素,从索引 0 开始。虽然什么都没移除,但这个操作会触发 PHP 对整个数组(或至少是受影响部分)进行重新索引,使其变为连续的数字键名。
代码示例
<?php
$myArray = [
1 => [ 'pid' => '915', 'size' => '2', 'count' => 11, 'fee' => '5500' ],
4 => [ 'pid' => '914', 'size' => '1', 'count' => 8, 'fee' => '1500' ],
6 => [ 'pid' => '913', 'size' => '2', 'count' => 5, 'fee' => '3200' ],
];
// 从索引 0 开始,移除 0 个元素。这会触发重新索引
array_splice($myArray, 0, 0, []);
print_r($myArray); // 注意:array_splice 是直接修改原数组的!
// 输出结果与前两种方法相同
/*
array(3) {
[0]=>
array(4) { ... }
[1]=>
array(4) { ... }
[2]=>
array(4) { ... }
}
*/
?>
优缺点
- 优点: 直接在原数组上操作,不需要创建新数组,可能在内存使用上稍微高效一点(取决于 PHP 内部实现)。
- 缺点: 代码意图不如
array_values()
清晰。array_splice($arr, 0, 0)
看起来有点像魔法咒语,可读性稍差。主要是用其副作用,而非其核心功能。对于新手来说可能比较困惑。性能上未必比array_values
快。
注意事项
array_splice
是直接修改 $myArray
本身的,而 array_values
和手动循环创建的是新数组,原数组 $myArray
保持不变(除非你把结果赋回给 $myArray
)。这点差异在使用时要特别留意。
方案四:JSON 编码再解码 (曲线救国)
还有个利用 JSON 转换特性的方法,虽然不常用,但也算个思路。
原理与作用
PHP 数组在 json_encode()
时,如果它是一个标准的、从 0 开始的连续数字索引数组,会被编码为 JSON 数组 [...]
。但如果它的键名不连续或者是关联数组,通常会被编码为 JSON 对象 {...}
。不过,对于我们这种情况(只有非连续数字键),json_encode
通常还是会把它当作一个稀疏数组处理,可能依然输出成 JSON 对象。
然而,当我们把一个表示“列表”(即使键名不连续)的 PHP 数组 json_encode()
,再用 json_decode()
解码回来时,如果 json_decode
的第二个参数是 false
或省略(默认返回对象),效果不明显。但如果第二个参数是 true
(要求返回关联数组),解码器会尝试将 JSON 数组 [...]
转回 PHP 的 0 索引数组。
更稳妥的做法是先用 array_values
(没错,又回到它了) 强制转成值列表,再编码解码,但这就没意义了。一个稍微不同的 JSON 技巧是,某些情况下 json_decode(json_encode($array), true)
能碰巧得到重排索引的效果,但这依赖具体数据结构和 PHP 版本行为,不保证稳定。
一个相对更可靠(但也更绕)的 JSON 相关的做法可能是:
<?php
$myArray = [
1 => [ 'pid' => '915', 'size' => '2', 'count' => 11, 'fee' => '5500' ],
4 => [ 'pid' => '914', 'size' => '1', 'count' => 8, 'fee' => '1500' ],
6 => [ 'pid' => '913', 'size' => '2', 'count' => 5, 'fee' => '3200' ],
];
// 1. 先用 array_values 得到值的列表(确保后续 JSON 编码为数组)
$valuesOnly = array_values($myArray);
// 2. 编码成 JSON 字符串
$jsonString = json_encode($valuesOnly);
// 3. 解码回 PHP 数组 (第二个参数 true 表示转为关联数组,但对纯数组没影响)
$reIndexedArray = json_decode($jsonString, true);
print_r($reIndexedArray);
// 输出结果仍然是正确的 0, 1, 2 索引数组
?>
但你看,这一通操作,核心还是第一步的 array_values()
。直接用 json_decode(json_encode($myArray), true)
对于示例中的不连续数字索引数组,行为可能不总是得到 0-based 索引,有时可能会保留原key转成string key的对象。
所以,这种 JSON 方法通常不推荐用来解决单纯的索引重排问题。它更像是某些特殊场景下的 Workaround,或者是在数据需要在不同系统间(如 PHP 与 JavaScript)传递时的副产品。
优缺点
- 优点: 无明显优点(针对此特定问题)。
- 缺点: 绕!性能开销大(涉及序列化和反序列化)。可能引入意想不到的数据类型转换(如数字变字符串、对象变数组等)。代码意图极其不清晰。强烈不推荐 用于解决这个问题。
总结一下
当你因为 unset
或其他操作导致 PHP 数组的数字索引不再连续,需要将它们重新整理成从 0 开始的连续索引时:
- 首选方案:
array_values($array)
。简洁、高效、意图明确。它就是干这个的。 - 备选方案:手动
foreach
循环构建新数组$newArray[] = $value
。代码稍多,但逻辑清晰,便于理解,且可以在循环中添加自定义处理逻辑。 - 可以但通常不推荐:
array_splice($array, 0, 0)
。利用副作用实现,可读性差,并且直接修改原数组。 - 强烈不推荐:JSON 编码解码技巧 。过于迂回,性能差,易出错。
根据你的具体需求和偏好,选择 array_values
或者手动循环通常是最好的选择。