第 8 章 — 数组
第 8 章 — 数组:索引数组、关联数组与数组函数
8.1 数组概述
PHP 数组实际上是有序映射(ordered map),可以将值映射到键。它是 PHP 中最灵活、最常用的数据结构。
<?php
// PHP 数组的底层实现:哈希表(Hash Table)
// 索引数组和关联数组在底层是同一种结构
8.2 创建数组
<?php
// 短语法(推荐)
$fruits = ['apple', 'banana', 'cherry'];
// array() 函数(传统)
$fruits = array('apple', 'banana', 'cherry');
// 空数组
$empty = [];
$empty = array();
// 关联数组
$user = [
'name' => 'Alice',
'age' => 30,
'email' => '[email protected]',
];
// 多维数组
$users = [
['name' => 'Alice', 'age' => 30],
['name' => 'Bob', 'age' => 25],
['name' => 'Eve', 'age' => 28],
];
// 数字键自动递增
$a = [];
$a[] = 'first'; // $a[0]
$a[] = 'second'; // $a[1]
$a[10] = 'ten';
$a[] = 'eleven'; // $a[11](自动从最大键 +1)
8.3 访问与修改
<?php
$arr = ['a', 'b', 'c'];
// 读取
echo $arr[0]; // a
echo $arr[count($arr) - 1]; // c(最后一个元素)
// 修改
$arr[1] = 'B';
// 添加
$arr[] = 'd'; // 追加到末尾
// 删除
unset($arr[2]); // 删除键 2(不会重新索引)
// 检查键是否存在
var_dump(isset($arr[0])); // true
var_dump(array_key_exists(0, $arr)); // true
var_dump(in_array('d', $arr)); // true
// 安全访问(PHP 7.4+)
$value = $arr[99] ?? 'default';
8.4 数组解构(Array Destructuring)
<?php
// 索引数组解构
[$first, $second, $third] = [1, 2, 3];
echo $first; // 1
// 跳过元素
[, $second, , $fourth] = [1, 2, 3, 4];
// 关联数组解构
['name' => $name, 'age' => $age] = ['name' => 'Alice', 'age' => 30];
// 函数返回值解构
function getUser(): array {
return ['name' => 'Bob', 'email' => '[email protected]', 'age' => 25];
}
['name' => $userName, 'email' => $userEmail] = getUser();
// foreach 中解构
$users = [
['name' => 'Alice', 'role' => 'admin'],
['name' => 'Bob', 'role' => 'user'],
];
foreach ($users as ['name' => $n, 'role' => $r]) {
echo "{$n}: {$r}\n";
}
8.5 常用数组函数
8.5.1 遍历与变换
| 函数 | 说明 | 示例 |
|---|---|---|
array_map() | 对每个元素应用回调 | array_map(fn($n) => $n * 2, [1,2,3]) |
array_filter() | 过滤元素 | array_filter([1,2,3], fn($n) => $n > 1) |
array_walk() | 遍历并修改(引用) | array_walk(&$arr, fn(&$v) => $v++) |
array_reduce() | 归约 | array_reduce([1,2,3], fn($c,$n) => $c+$n, 0) |
array_column() | 提取一列 | array_column($users, 'name') |
<?php
// array_map — 批量转换
$prices = [100, 200, 300];
$withTax = array_map(fn($p) => round($p * 1.13, 2), $prices);
// [113.0, 226.0, 339.0]
// array_filter — 过滤
$numbers = range(1, 20);
$even = array_filter($numbers, fn($n) => $n % 2 === 0);
// [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
// 保留键
$adults = array_filter($users, fn($u) => $u['age'] >= 18);
// array_reduce — 归约
$sum = array_reduce([1, 2, 3, 4, 5], fn($carry, $item) => $carry + $item, 0);
// 15
// array_column — 提取列
$names = array_column($users, 'name');
// ['Alice', 'Bob', 'Eve']
// 以 id 为键
$byId = array_column($users, null, 'id');
8.5.2 查找与搜索
<?php
$items = ['apple', 'banana', 'cherry', 'date'];
// 检查值
in_array('banana', $items); // true
in_array('Banana', $items); // false(区分大小写)
in_array('Banana', $items, true); // false(严格比较)
// 查找键
array_search('cherry', $items); // 2
// 检查键
array_key_exists('name', $user); // true
isset($user['name']); // true(更严格,null 时返回 false)
// 查找满足条件的元素
$found = array_find([1, 2, 3, 4], fn($n) => $n > 2); // PHP 8.4+
$key = array_find_key($users, fn($u) => $u['name'] === 'Alice'); // PHP 8.4+
$any = array_any($users, fn($u) => $u['age'] > 25); // PHP 8.4+
$all = array_all($users, fn($u) => $u['age'] > 0); // PHP 8.4+
8.5.3 排序
<?php
$numbers = [3, 1, 4, 1, 5, 9, 2, 6];
// 基本排序(不保留键)
sort($numbers); // [1, 1, 2, 3, 4, 5, 6, 9]
rsort($numbers); // [9, 6, 5, 4, 3, 2, 1, 1]
// 保留键的排序
$prices = ['apple' => 5, 'banana' => 3, 'cherry' => 8];
asort($prices); // 按值升序,保留键
arsort($prices); // 按值降序,保留键
ksort($prices); // 按键升序
krsort($prices); // 按键降序
// 自定义排序
usort($users, fn($a, $b) => $a['age'] <=> $b['age']); // 按年龄排序
uasort($prices, fn($a, $b) => $b <=> $a); // 按值降序,保留键
uksort($prices, fn($a, $b) => strcmp($a, $b)); // 按键名字母排序
// 多字段排序
usort($users, function($a, $b) {
$cmp = $a['age'] <=> $b['age'];
return $cmp !== 0 ? $cmp : strcmp($a['name'], $b['name']);
});
// PHP 8.0+ array_multisort
$ages = array_column($users, 'age');
array_multisort($ages, SORT_ASC, $users);
8.5.4 合并与拆分
<?php
$a = [1, 2, 3];
$b = [4, 5, 6];
// 合并
$merged = array_merge($a, $b); // [1,2,3,4,5,6]
$spread = [...$a, ...$b]; // [1,2,3,4,5,6](PHP 7.4+)
// 关联数组合并(后者覆盖前者)
$defaults = ['color' => 'blue', 'size' => 'medium'];
$custom = ['color' => 'red'];
$final = array_merge($defaults, $custom); // ['color'=>'red', 'size'=>'medium']
$final = [...$defaults, ...$custom]; // 同上
// 交集与差集
$set1 = [1, 2, 3, 4];
$set2 = [3, 4, 5, 6];
array_intersect($set1, $set2); // [3, 4](值比较)
array_diff($set1, $set2); // [1, 2]
// 分片
$slice = array_slice([1,2,3,4,5], 1, 3); // [2, 3, 4]
// 填充
$filled = array_fill(0, 5, 'x'); // ['x','x','x','x','x']
$range = range(1, 10, 2); // [1, 3, 5, 7, 9]
// 打乱
$shuffled = [1,2,3,4,5];
shuffle($shuffled);
// 唯一值
$duplicated = [1, 2, 2, 3, 3, 3];
$unique = array_unique($duplicated); // [1, 2, 3]
8.6 数组运算符
<?php
$a = ['color' => 'red', 'size' => 10];
$b = ['color' => 'blue', 'weight' => 5];
// 联合(+)— 左边优先,不覆盖
$union = $a + $b;
// ['color' => 'red', 'size' => 10, 'weight' => 5]
// 相等(==)
var_dump(['a' => 1] == ['a' => 1]); // true
var_dump(['a' => 1] == ['a' => '1']); // true(松散比较)
// 全等(===)
var_dump(['a' => 1] === ['a' => 1]); // true
var_dump(['a' => 1] === ['a' => '1']); // false
// 差集
$a - $b; // 键在 $a 中但不在 $b 中的元素
// 交集
$a & $b; // 键同时在 $a 和 $b 中的元素
8.7 集合操作模式
<?php
declare(strict_types=1);
class Collection
{
private array $items;
public function __construct(array $items = [])
{
$this->items = array_values($items);
}
public function map(callable $callback): self
{
return new self(array_map($callback, $this->items));
}
public function filter(callable $callback): self
{
return new self(array_filter($this->items, $callback));
}
public function reduce(callable $callback, mixed $initial = null): mixed
{
return array_reduce($this->items, $callback, $initial);
}
public function first(?callable $callback = null): mixed
{
if ($callback === null) return $this->items[0] ?? null;
foreach ($this->items as $item) {
if ($callback($item)) return $item;
}
return null;
}
public function sortBy(callable $callback): self
{
$items = $this->items;
usort($items, fn($a, $b) => $callback($a) <=> $callback($b));
return new self($items);
}
public function groupBy(callable $callback): array
{
$groups = [];
foreach ($this->items as $item) {
$key = $callback($item);
$groups[$key][] = $item;
}
return $groups;
}
public function toArray(): array
{
return $this->items;
}
public function count(): int
{
return count($this->items);
}
}
// 使用示例
$users = [
['name' => 'Alice', 'dept' => 'Engineering', 'salary' => 12000],
['name' => 'Bob', 'dept' => 'Marketing', 'salary' => 8000],
['name' => 'Eve', 'dept' => 'Engineering', 'salary' => 15000],
['name' => 'Dave', 'dept' => 'Marketing', 'salary' => 9000],
];
$engNames = (new Collection($users))
->filter(fn($u) => $u['dept'] === 'Engineering')
->sortBy(fn($u) => -$u['salary'])
->map(fn($u) => $u['name'])
->toArray();
// ['Eve', 'Alice']
$totalSalary = (new Collection($users))
->reduce(fn($sum, $u) => $sum + $u['salary'], 0);
// 44000
$byDept = (new Collection($users))
->groupBy(fn($u) => $u['dept']);
// ['Engineering' => [...], 'Marketing' => [...]]
8.8 SPL 数据结构
<?php
// SplFixedArray — 固定大小数组(性能更好)
$arr = new SplFixedArray(5);
$arr[0] = 'a';
$arr[1] = 'b';
// $arr[5] = 'c'; // RuntimeException(越界)
// SplStack — 栈(后进先出)
$stack = new SplStack();
$stack->push('first');
$stack->push('second');
echo $stack->pop(); // second
// SplQueue — 队列(先进先出)
$queue = new SplQueue();
$queue->enqueue('first');
$queue->enqueue('second');
echo $queue->dequeue(); // first
// SplPriorityQueue — 优先队列
$pq = new SplPriorityQueue();
$pq->insert('low task', 1);
$pq->insert('high task', 10);
$pq->insert('medium task', 5);
echo $pq->extract(); // high task(最高优先级)
// SplHeap — 堆
// SplMinHeap / SplMaxHeap
// SplObjectStorage — 对象集合
$storage = new SplObjectStorage();
$obj1 = new stdClass();
$storage->attach($obj1, 'some data');
$storage->contains($obj1); // true
8.9 业务场景:分页计算
<?php
declare(strict_types=1);
class Paginator
{
public function __construct(
private readonly int $totalItems,
private readonly int $perPage = 15,
private readonly int $currentPage = 1,
) {}
public function getTotalPages(): int
{
return max(1, (int)ceil($this->totalItems / $this->perPage));
}
public function getOffset(): int
{
return ($this->currentPage - 1) * $this->perPage;
}
public function getLimit(): int
{
return $this->perPage;
}
public function getPages(): array
{
$total = $this->getTotalPages();
$current = $this->currentPage;
$delta = 2;
$range = range(
max(1, $current - $delta),
min($total, $current + $delta)
);
$pages = [];
if ($range[0] > 1) {
$pages[] = 1;
if ($range[0] > 2) $pages[] = '...';
}
$pages = array_merge($pages, $range);
if (end($range) < $total) {
if (end($range) < $total - 1) $pages[] = '...';
$pages[] = $total;
}
return $pages;
}
public function toArray(): array
{
return [
'current_page' => $this->currentPage,
'per_page' => $this->perPage,
'total_items' => $this->totalItems,
'total_pages' => $this->getTotalPages(),
'has_prev' => $this->currentPage > 1,
'has_next' => $this->currentPage < $this->getTotalPages(),
'pages' => $this->getPages(),
];
}
}
// 使用
$paginator = new Paginator(totalItems: 157, perPage: 15, currentPage: 5);
print_r($paginator->toArray());
// [current_page] => 5
// [per_page] => 15
// [total_items] => 157
// [total_pages] => 11
// [has_prev] => 1
// [has_next] => 1
// [pages] => Array (1, '...', 3, 4, 5, 6, 7, '...', 11)
8.10 扩展阅读
上一章:第 7 章 — 函数 下一章:第 9 章 — 字符串