强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

PHP 完全指南 / 第 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 章 — 字符串