数组遍历
概述
PHP 提供了多种遍历数组的方式,foreach 是最常用和推荐的方式。本章详细讲解 foreach 遍历、list/短语法解构、array_walk、array_map、array_filter 同时获取键值,以及 each() 已移除的说明。
基础概念
| 方式 | 获取键 | 获取值 | 可修改原数组 | 适用场景 |
|---|---|---|---|---|
foreach | 是 | 是 | 是(引用) | 通用遍历 |
array_walk | 是 | 是 | 是(引用) | 原地修改 |
array_map | 否(需 array_keys) | 是 | 否 | 转换映射 |
array_filter | 需 ARRAY_FILTER_USE_BOTH | 是 | 否 | 筛选过滤 |
for | 通过 count | 通过索引 | 是 | 索引数组 |
each() | 是 | 是 | 否 | PHP 8.0+ 已移除 |
each() 已移除
each() 函数在 PHP 7.2 被废弃,PHP 8.0 完全移除。请使用 foreach 替代。
语法与代码
foreach 基本遍历
php
<?php
declare(strict_types=1);
// 遍历值
$fruits = ['apple', 'banana', 'cherry'];
foreach ($fruits as $fruit) {
echo $fruit . "\n";
}
// 遍历键值对
$user = ['name' => 'Alice', 'age' => 30, 'email' => 'a@b.com'];
foreach ($user as $key => $value) {
echo "{$key}: {$value}\n";
}
// PHP 7.0+ 引用遍历(可修改原数组)
$prices = [100, 200, 300];
foreach ($prices as &$price) {
$price *= 0.9; // 打九折
}
unset($price); // 重要:解除引用
print_r($prices);
// [90, 180, 270]foreach 解构遍历
php
<?php
declare(strict_types=1);
// PHP 7.1+ 短语法解构
$users = [
['name' => 'Alice', 'age' => 30],
['name' => 'Bob', 'age' => 25],
['name' => 'Charlie', 'age' => 35],
];
foreach ($users as ['name' => $name, 'age' => $age]) {
echo "{$name} is {$age} years old\n";
}
// PHP 7.1+ list() 解构(也兼容)
foreach ($users as list('name' => $name, 'age' => $age)) {
echo "{$name}: {$age}\n";
}
// 跳过元素
$records = [[1, 'Alice', 30], [2, 'Bob', 25]];
foreach ($records as [$id, $name]) {
echo "#{$id}: {$name}\n";
}array_walk — 原地修改
php
<?php
declare(strict_types=1);
// array_walk — 对每个元素执行回调(可修改原数组)
$data = ['name' => 'alice', 'email' => 'ALICE@EXAMPLE.COM'];
// 值引用修改
array_walk($data, function (mixed &$value, string $key): void {
$value = ucfirst($value);
});
print_r($data);
// ['name' => 'Alice', 'email' => 'ALICE@example.com']
// array_walk 配合引用修改多维数组
$items = [
['name' => 'item1', 'price' => 100],
['name' => 'item2', 'price' => 200],
];
array_walk($items, function (array &$item): void {
$item['price'] *= 1.1; // 加价 10%
$item['final'] = true;
});
print_r($items);array_map — 转换映射
php
<?php
declare(strict_types=1);
// array_map — 对每个元素应用回调,返回新数组
$numbers = [1, 2, 3, 4, 5];
$doubled = array_map(fn(int $n): int => $n * 2, $numbers);
// [2, 4, 6, 8, 10]
// 同时处理键和值
$user = ['name' => 'alice', 'age' => '30'];
// array_map 默认不传键,需要额外处理
$mapped = array_map(fn(string $key, mixed $val): string => ucfirst($key) . ': ' . $val,
array_keys($user),
$user
);
// ['Name: alice', 'Age: 30']array_filter 同时获取键值(PHP 5.6+)
php
<?php
declare(strict_types=1);
// array_filter — 使用 ARRAY_FILTER_USE_BOTH 同时获取键和值
$products = [
'apple' => ['price' => 5, 'stock' => 100],
'banana' => ['price' => 3, 'stock' => 0],
'cherry' => ['price' => 8, 'stock' => 50],
];
// 过滤有库存且价格 > 4 的商品
$available = array_filter(
$products,
fn(string $key, array $value): bool => $value['stock'] > 0 && $value['price'] > 4,
ARRAY_FILTER_USE_BOTH
);
print_r($available);
// ['apple' => [...], 'cherry' => [...]]for 循环遍历索引数组
php
<?php
declare(strict_types=1);
$items = ['a', 'b', 'c', 'd', 'e'];
$count = count($items);
for ($i = 0; $i < $count; $i++) {
echo "{$i}: {$items[$i]}\n";
}
// 需要索引时使用 for
// 注意:count() 在循环前调用一次(不在条件中每次调用)详细说明
foreach 引用陷阱
php
<?php
declare(strict_types=1);
// 常见陷阱:引用遍历后未 unset
$colors = ['red', 'green', 'blue'];
foreach ($colors as &$color) {
$color = strtoupper($color);
}
// 如果这里没有 unset($color)...
unset($color); // 必须解除引用
// 继续使用 $color 不会意外修改数组最后一个元素
$color = 'yellow';
print_r($colors);
// ['RED', 'GREEN', 'BLUE'](不受影响)遍历中删除元素
php
<?php
declare(strict_types=1);
// 安全方式:先收集要删除的键,再统一删除
$data = ['a' => 1, 'b' => 2, 'c' => 3, 'd' => 4];
$toRemove = [];
foreach ($data as $key => $value) {
if ($value % 2 === 0) {
$toRemove[] = $key;
}
}
foreach ($toRemove as $key) {
unset($data[$key]);
}
// ['a' => 1, 'c' => 3]
// 也可以用 array_filter
$data = array_filter($data, fn(int $v): bool => $v % 2 !== 0);实战示例
数据转换管道
php
<?php
declare(strict_types=1);
$employees = [
['name' => 'Alice', 'salary' => 5000, 'department' => 'engineering'],
['name' => 'Bob', 'salary' => 3000, 'department' => 'marketing'],
['name' => 'Charlie', 'salary' => 7000, 'department' => 'engineering'],
['name' => 'Diana', 'salary' => 4000, 'department' => 'marketing'],
];
// 筛选工程部员工
$engineers = array_filter(
$employees,
fn(array $e): bool => $e['department'] === 'engineering'
);
// 提取姓名和年薪
$result = array_map(
fn(array $e): array => [
'name' => $e['name'],
'annual' => $e['salary'] * 12,
],
array_values($engineers)
);
print_r($result);
// [['name' => 'Alice', 'annual' => 60000], ['name' => 'Charlie', 'annual' => 84000]]array_column 提取数据
php
<?php
declare(strict_types=1);
$users = [
['id' => 1, 'name' => 'Alice', 'role' => 'admin'],
['id' => 2, 'name' => 'Bob', 'role' => 'user'],
['id' => 3, 'name' => 'Charlie', 'role' => 'user'],
];
// 提取单列
$names = array_column($users, 'name');
// ['Alice', 'Bob', 'Charlie']
// 指定键列
$byId = array_column($users, 'name', 'id');
// [1 => 'Alice', 2 => 'Bob', 3 => 'Charlie']
// 提取对象数组列
class Product
{
public function __construct(public string $name, public float $price) {}
}
$products = [new Product('Apple', 3.5), new Product('Banana', 2.0)];
$names = array_column($products, 'name');
// ['Apple', 'Banana'](PHP 7.0+ 支持对象数组)array_map 同时处理键值
php
<?php
declare(strict_types=1);
// array_map 同时处理键和值的技巧
$config = ['debug' => true, 'cache_time' => 3600, 'max_connections' => 100];
// 方式 1:使用 array_map + array_keys
$result = array_map(
fn(string $key, mixed $val): string => strtoupper($key) . ': ' . var_export($val, true),
array_keys($config),
$config
);
// ['DEBUG: true', 'CACHE_TIME: 3600', 'MAX_CONNECTIONS: 100']
// 方式 2:使用 array_walk(原地修改)
array_walk($config, function (mixed &$val, string $key): void {
$val = strtoupper($key) . ': ' . var_export($val, true);
});
// 方式 3:使用 foreach(最灵活)
$result = [];
foreach ($config as $key => $val) {
$result[strtoupper($key)] = is_bool($val) ? ($val ? 'yes' : 'no') : $val;
}生成器与数组遍历
php
<?php
declare(strict_types=1);
// 使用生成器进行惰性遍历
function lazyFilter(iterable $items, callable $callback): \Generator
{
foreach ($items as $key => $item) {
if ($callback($item)) {
yield $key => $item;
}
}
}
function lazyMap(iterable $items, callable $callback): \Generator
{
foreach ($items as $key => $item) {
yield $key => $callback($item);
}
}
// 大数据集处理
$data = range(1, 1000000);
$result = lazyFilter(
lazyMap($data, fn(int $n): int => $n * 2),
fn(int $n): bool => $n > 100 && $n % 3 === 0
);
// 只在需要时计算,内存占用极小
$count = 0;
foreach ($result as $value) {
$count++;
if ($count > 10) break; // 只取前 10 个
}
echo "Found at least 10 items\n";性能建议
对于超大数据集(百万级),使用生成器代替数组遍历,避免内存溢出。生成器只在需要时计算每个元素。
遍历中的内存优化
php
<?php
declare(strict_types=1);
// yield 生成器减少内存
function readLargeFile(string $path): \Generator
{
$handle = fopen($path, 'r');
if (!$handle) return;
while (($line = fgets($handle)) !== false) {
yield trim($line);
}
fclose($handle);
}
// 逐行处理大文件,内存占用极小
$count = 0;
foreach (readLargeFile('/var/log/syslog') as $line) {
if (str_contains($line, 'ERROR')) {
$count++;
}
}
echo "Error lines: {$count}\n";foreach vs for
foreach 更简洁安全,for 在需要索引时更直观。优先使用 foreach。
注意事项
foreach 引用
使用引用遍历时,循环结束后务必 unset($ref) 解除引用,否则后续对 $ref 的赋值会修改数组最后一个元素。
遍历中修改数组
在 foreach 遍历中直接 unset() 元素是安全的,但添加元素的行为不确定。建议先收集操作,遍历结束后统一执行。
最佳实践
- 优先使用 foreach:最简洁、最安全的遍历方式
- 遍历多维用解构:
foreach ($arr as ['key' => $val]) - 原地修改用 array_walk:需要修改原数组时
- 转换用 array_map:需要返回新数组时
- 筛选用 array_filter:保留/过滤元素
- 需要键值用 ARRAY_FILTER_USE_BOTH