前言
PHP 的数组(array)是这门语言里被使用频率最高的复合类型,也是被误解最多的类型之一。很多教程把它讲成「两种数组」——索引数组和关联数组,仿佛它们是两个不同的东西。实际上在 PHP 内部只有一种结构:有序映射(ordered map)。它同时承担了列表、字典、栈、队列、集合等多种角色,所谓「索引数组」只是「键恰好是从 0 开始的连续整数」这一种特例。
第二个常见的误解是把 PHP 数组当成 C 语言那种连续内存。它不是。PHP 数组底层是哈希表(HashTable)加一条双向链表,链表保证了插入顺序即遍历顺序,哈希表保证了按键存取是常数级复杂度。理解这一点,很多事情就顺了:为什么unset()掉一个元素后剩下的键不会自动补齐,为什么array_merge()会把数字键重排而字符串键不重排。
本文从数组的结构本质讲起,然后重点落在几个最高频函数的取舍上:count()、array_keys()、array_values()、array_flip()、array_sum()。这几个函数单看都不难,难的是「这件事到底该用哪个」,以及它们各自在什么输入下会给你一个意外结果。示例以 PHP 8.0 及以上为基准。
一、数组的结构本质与创建方式
PHP 数组的键只允许两种类型:整数(int)和字符串(string)。其余类型的键会被强制转换,规则是固定的,不是随机的:
| 原始键 | 转换结果 | 说明 |
|---|
"8" | 整数8 | 合法的十进制整数字符串会被转成整数 |
"08" | 字符串"08" | 前导零不算合法整数字符串,保持原样 |
8.7 | 整数8 | 浮点键截断取整,PHP 8.1 起会发出废弃提示 |
true/false | 1/0 | 布尔键转成 0 或 1 |
null | "" | 空字符串键 |
| 数组、对象 | 抛TypeError | PHP 8 起是Illegal offset type的 TypeError |
创建方式有多种,下面这段代码覆盖了最常用的几种:
<?php // 适用于 PHP 8.0+
// 1. 字面量:索引数组,键自动从 0 开始
$list = ['apple', 'banana', 'cherry'];
// 2. 字面量:关联数组,键由你自己指定
$user = ['name' => 'Ada', 'age' => 36];
// 3. 逐个赋值,追加时用空的方括号
$stack = [];
$stack[] = 'first'; // 键为 0
$stack[] = 'second'; // 键为 1
// 4. array_fill():用同一个值填充一段连续整数键
$zeros = array_fill(0, 3, 0); // [0 => 0, 1 => 0, 2 => 0]
// 5. array_fill_keys():给一批指定的键填同一个值
$flags = array_fill_keys(['a', 'b'], false); // ['a' => false, 'b' => false]
// 6. range():生成一段范围
$odds = range(1, 9, 2); // [1, 3, 5, 7, 9]
// 7. 解构赋值(PHP 7.1 起支持短语法)
[$first, $second] = $list;
print_r($list);
print_r($user);
print_r($zeros);
print_r($flags);
print_r($odds);PHP 8.1 新增了array_is_list(),用来判断一个数组是不是「真正的列表」:键从 0 开始、连续、且全是整数。这个函数解决了一个长期痛点——以前判断一个数组是不是索引数组,只能写array_keys($a) === range(0, count($a) - 1)这种绕圈子的写法。
<?php // 需要 PHP 8.1 及以上(array_is_list)
var_dump(array_is_list([1, 2, 3])); // true
var_dump(array_is_list([0 => 1, 2 => 3])); // false,键不连续
var_dump(array_is_list(['a' => 1])); // false,键不是整数PHP 8.1 以下的替代写法就是前面那句array_keys($a) === range(0, count($a) - 1),注意count($a)为 0 时range(0, -1)返回[0, -1],需要额外判空。
二、键与值的互转:array_keys 与 array_values
array_keys()和array_values()是一对方向相反的函数,签名如下:
array_keys(array $array, mixed $filter_value = null, bool $strict = false): array
array_values(array $array): arrayarray_values()做的是「丢掉所有键,重新从 0 开始编号」,返回一个新数组,原数组不受影响。它最典型的用途是修复被unset()弄出窟窿的数组,让json_encode()输出真正的 JSON 数组而不是 JSON 对象:
<?php // 适用于 PHP 8.0+
$raw = ['a' => 1, 'b' => 2, 'c' => 3];
unset($raw['b']);
echo json_encode($raw), PHP_EOL; // {"a":1,"c":3} 这是对象
echo json_encode(array_values($raw)), PHP_EOL; // [1,3] 这才是数组这个差别在实际项目里非常致命:前端拿到{"a":1,"c":3}去遍历,得到的是一个 JavaScript 对象;拿到[1,3]才是数组。同一个接口,unset()前后返回的结构类型都变了。
array_keys()除了取键,还能做「按键反查值的位置」:第二个参数给了值,它就返回拥有该值的所有键(可能有多个),第三个参数决定比较是否严格。这里要注意:$filter_value的默认值null表示的是「不做过滤」而不是「过滤出值为null的键」,所以想找值为null的键不能靠它,得先array_filter($a, 'is_null')拿到元素再取键。
<?php // 适用于 PHP 8.0+
$cfg = ['host' => 'db', 'port' => '3306', 'retry' => 3];
print_r(array_keys($cfg)); // [0=>'host', 1=>'port', 2=>'retry']
print_r(array_keys($cfg, 3)); // [0=>'retry']
$mixed = [1, '1', 2, '2'];
print_r(array_keys($mixed, 1)); // [0, 1] 松散比较,'1' 也命中
print_r(array_keys($mixed, 1, true)); // [0] 严格比较,只命中整数 1
// PHP 7.3 起还有取首尾键的快捷函数
echo array_key_first($cfg), PHP_EOL; // host
echo array_key_last($cfg), PHP_EOL; // retry三、值到键的反转与求和:array_flip 与 array_sum
array_flip()交换键和值。它有一条硬性限制:只有 int 和 string 类型的值才能当键。遇到别的类型的值,该条目会被跳过,PHP 8 会发出一条Warning: array_flip(): Can only flip STRING and INTEGER values。另外,如果原来的值有重复,反转时后面的会覆盖前面的。
<?php // 适用于 PHP 8.0+
$status = ['pending' => 0, 'paid' => 1, 'void' => 2];
// 反转后得到「编号 => 名称」,这是把状态码翻译成文字的最省事写法
$names = array_flip($status);
print_r($names); // [0=>'pending', 1=>'paid', 2=>'void']
echo $names[1], PHP_EOL; // paid —— 直接用数字下标取名字
// 重复值会被吞掉
print_r(array_flip(['x' => 'a', 'y' => 'a'])); // ['a' => 'y'],'x' 没了array_sum()累加数组中所有值,返回int或float。它的输入要求是「值必须是数字或数字字符串」,混入非数字内容时结果不可靠。稳妥的做法是先过滤:
<?php // 适用于 PHP 8.0+
$prices = ['12.5', '7', 3];
// 稳妥写法:先过滤出数字,再求和
$nums = array_filter($prices, 'is_numeric');
echo array_sum($nums), PHP_EOL; // 22.5
// 只想统计数组里有几个元素,别用 array_sum,用 count
echo count($prices), PHP_EOL; // 3注意array_sum()是纯粹的数值累加,它不会去重、不会跳过字符串键、也不会因为值是负号开头就特殊处理。array_sum()和count()经常被初学者混用,前者求和、后者计数,是完全不同的两件事;如果只想数个数,用array_sum()只会得到一堆无意义的加法结果。
四、五个高频函数该怎么选
把上面的取舍整理成一张对照表:
| 需求 | 该用的函数 | 不该用的 | 原因 |
|---|
| 数元素个数 | count() | sizeof()(别名,没必要)、array_sum() | count()对Countable对象也有效;array_sum()是求和不是计数 |
| 取一批键 | array_keys() | foreach手动收集 | 内建函数一步到位,还能按值过滤 |
| 丢掉键重新编号 | array_values() | foreach重建 | array_values()保留了内部顺序,语义更直接 |
| 值当键反查 | array_flip() | 手写两层循环 | 但仅限 int/string 值,且重复值会丢失 |
| 求和 | array_sum() | 手写+=循环 | 内建实现更紧凑;注意先is_numeric过滤 |
count()还有一个容易忽略的坑:在 PHP 8 里对不可数的值调用它会抛TypeError,而 PHP 7.x 只是发警告并返回 1。所以从$_POST、函数返回值这类「可能是null」的地方拿到变量后,直接count()是不安全的:
<?php // 适用于 PHP 8.0+
function maybe(): ?array {
return null;
}
$maybe = maybe();
// ❌ 错:PHP 8 下 count(null) 抛 TypeError
// echo count($maybe);
// ✅ 对:PHP 7.3 起有 is_countable()
echo is_countable($maybe) ? count($maybe) : 0, PHP_EOL; // 0常见坑点
- 以为数字字符串键还是字符串
❌$a["1"] = 'x'; var_dump(array_key_first($a));得到string(1) "1"—— 错,得到的是int(1)✅ 记住键会被强制转换:"1"变1,"01"保持字符串,true变1,8.9变8(PHP 8.1 起发废弃提示)
- 用
unset()删元素后直接丢给前端
❌unset($a[1]); echo json_encode($a);得到 JSON 对象而不是数组 ✅echo json_encode(array_values($a));,或改用array_splice()直接在原地重排
- 拿
sizeof()当「另一个函数」用
❌ 以为sizeof()是求内存占用或字节大小 ✅sizeof()只是count()的别名,行为完全一致;要字节数得用别的手段,它做不到
array_flip()后直接按原值取值
❌ 对['a' => true]做array_flip()期望得到[true => 'a']—— 错,布尔值不能当键,该条目被跳过并报警告 ✅ 只对 int/string 值使用array_flip();其他类型先在array_map()里转成字符串再反转
- 用
array_sum()代替count()
❌array_sum([1, 1, 1])得到3,就以为数组有 3 个元素 —— 换成[2, 2, 2]就得到6✅ 计数用count(),求和用array_sum(),两者语义不同
- 在 PHP 8 里对可能为
null的变量直接count()
❌count($maybe)抛TypeError: count(): Argument #1 ($value) must be of type Countable|array, null given✅ 先is_countable()判断,或把函数返回值统一改成返回空数组
- 以为数组里元素是假值,整个数组就是假值
❌if (!$arr)来判断「数组里没有有效数据」——[0, '']是非空数组,!$arr为false✅ 判断数组是否为空用$arr === []或count($arr) === 0;判断内容是否有效要另外过滤
- 以为
array_map()能改键
❌array_map('strtoupper', $arr)想把键改成大写 —— 它是「映射值」的函数:只传一个数组时键原样保留,传多个数组时所有键都会被重新索引 ✅ 改键用array_combine()或foreach重建;单纯改键名大小写用array_change_key_case()
总结
| 知识点 | 结论 |
|---|
| 数组本质 | 有序哈希表,插入顺序即遍历顺序,键只能是 int 或 string |
| 键类型转换 | "8"→8,"08"保持字符串,true→1,null→"" |
count() | 数元素个数;PHP 8 起对不可数类型抛TypeError,用is_countable()兜底 |
array_keys()/array_values() | 取键 / 丢键重排;后者是修 JSON 结构的常用手段 |
array_flip() | 仅 int/string 值可用,重复值后者胜,非法的值会被跳过 |
array_sum() | 只做数值累加,混入非数字内容前先is_numeric过滤 |
array_is_list() | PHP 8.1 起可用,判断是否「真列表」 |
PHP 数组用起来门槛很低,但它同时是列表、字典和栈,同一段代码在不同键分布下的行为可能完全不同。把「有序哈希表」这个底层模型记住,再遇到unset()后的 JSON 变形、字符串数字键变成整数、count(null)抛异常这类问题时,就不会觉得莫名其妙了。