PHP 陣列操作大全 (下):進階函數、多維處理、效能優化與實戰模式
承接上篇,本文深入探討陣列進階操作:排序家族、合併差集交集、棧隊列模擬、遞歸處理、SPL 陣列物件、生成器配合、記憶體優化與實戰最佳實踐。
一、排序函數家族完整對比
基本排序 (修改原陣列,返回 bool)
| 函數 | 排序依據 | 保留鍵名 | 穩定性 |
| sort() | 值升序 | 否 (重新索引 0...) | 不穩定 |
| rsort() | 值降序 | 否 | 不穩定 |
| asort() | 值升序 | 是 | PHP 8.0+ 穩定 |
| arsort() | 值降序 | 是 | PHP 8.0+ 穩定 |
| ksort() | 鍵升序 | 是 | 穩定 |
| krsort() | 鍵降序 | 是 | 穩定 |
| natsort() | 自然排序 (值) | 是 | 穩定 |
| natcasesort() | 自然排序忽略大小寫 | 是 | 穩定 |
| usort() | 自定義比較 (值) | 否 | 不穩定 |
| uksort() | 自定義比較 (鍵) | 是 | 不穩定 |
| uasort() | 自定義比較 (值) + 保留鍵 | 是 | PHP 8.0+ 穩定 |
自然排序實戰
<?php
$files = ['img1.png', 'img10.png', 'img2.png', 'img20.png', 'img3.png'];
sort($files); // ['img1.png','img10.png','img2.png','img20.png','img3.png'] 字典序
natsort($files); // ['img1.png','img2.png','img3.png','img10.png','img20.png'] 自然序
// 版本號排序
$versions = ['1.10.0', '1.2.0', '1.1.0', '2.0.0', '1.9.0'];
natsort($versions); // ['1.1.0','1.2.0','1.9.0','1.10.0','2.0.0']
?>
自定義排序 usort/uasort/uksort
<?php
$users = [
['name' => 'Alice', 'age' => 25, 'score' => 90],
['name' => 'Bob', 'age' => 30, 'score' => 85],
['name' => 'Charlie', 'age' => 25, 'score' => 95]
];
// 多字段排序:年齡升序,分數降序
usort($users, function($a, $b) {
// 年齡升序
$ageCmp = $a['age'] <=> $b['age'];
if ($ageCmp !== 0) return $ageCmp;
// 年齡相同,分數降序
return $b['score'] <=> $a['score'];
});
/*
結果:
Alice (25, 90)
Charlie (25, 95) -> 分數高在前
Bob (30, 85)
*/
// PHP 8.0+ 穩定排序保證:相同年齡分數的保持原序
?>
多維/多陣列排序 array_multisort
<?php
// 場景:多個並行陣列按某欄位排序
$names = ['Alice', 'Bob', 'Charlie'];
$ages = [25, 30, 20];
$scores = [90, 85, 95];
// 按年齡升序排序,並行陣列同步調整
array_multisort($ages, SORT_ASC, $names, $scores);
/*
$ages: [20, 25, 30]
$names: ['Charlie', 'Alice', 'Bob']
$scores: [95, 90, 85]
*/
// 多維陣列排序 (提取欄位)
$data = [
['name' => 'Alice', 'age' => 25],
['name' => 'Bob', 'age' => 30],
['name' => 'Charlie', 'age' => 20]
];
$ages = array_column($data, 'age');
array_multisort($ages, SORT_ASC, $data);
// $data 按 age 升序重排
?>
二、陣列合併、差集、交集進階
array_merge 變體
<?php
// array_merge_recursive: 衝突鍵合併為陣列
$a = ['color' => ['red', 'blue'], 'size' => 'M'];
$b = ['color' => ['green'], 'price' => 100];
print_r(array_merge_recursive($a, b));
/*
[color] => Array([0]=>red, [1]=>blue, [2]=>green)
[size] => M
[price] => 100
*/
// array_replace: 後者覆蓋,不重新索引數字鍵
$a = [0 => 'a', 1 => 'b', 'x' => 1];
$b = [1 => 'B', 2 => 'c', 'x' => 2];
print_r(array_replace($a, $b));
// [0=>'a', 1=>'B', 2=>'c', 'x'=>2]
// array_replace_recursive: 遞歸替換
$a = ['db' => ['host' => 'localhost', 'port' => 3306]];
$b = ['db' => ['user' => 'root']];
print_r(array_replace_recursive($a, $b));
// ['db' => ['host'=>'localhost', 'port'=>3306, 'user'=>'root']]
?>
差集與交集家族
| 函數 | 比較方式 | 返回 | 鍵名保留 |
| array_diff | 值 | $a 中不在其它陣列的值 | $a 的鍵 |
| array_diff_assoc | 鍵+值 | $a 中鍵值對不在其它的 | $a 的鍵 |
| array_diff_key | 鍵 | $a 中鍵不在其它的 | $a 的鍵 |
| array_diff_uassoc | 鍵+值 (自定義鍵比較) | ... | $a 的鍵 |
| array_intersect | 值 | 同時在所有陣列的值 | 第一個陣列的鍵 |
| array_intersect_assoc | 鍵+值 | ... | 第一個陣列的鍵 |
| array_intersect_key | 鍵 | ... | 第一個陣列的鍵 |
| array_intersect_uassoc | 鍵+值 (自定義鍵比較) | ... | 第一個陣列的鍵 |
實戰:配置差異檢測
<?php
$defaultConfig = [
'debug' => false,
'cache' => true,
'db' => ['host' => 'localhost', 'port' => 3306],
'allowed_ips' => ['10.0.0.1', '10.0.0.2']
];
$envConfig = [
'debug' => true,
'db' => ['host' => 'db-server', 'user' => 'app'],
'allowed_ips' => ['10.0.0.1', '10.0.0.3', '10.0.0.4']
];
// 扁平化比較 (需自行遞歸)
function arrayDiffRecursive(array $a, array $b): array {
$diff = [];
foreach ($a as $k => $v) {
if (!array_key_exists($k, $b)) {
$diff[$k] = ['old' => $v, 'new' => null]; // 刪除
} elseif (is_array($v) && is_array($b[$k])) {
$subDiff = arrayDiffRecursive($v, $b[$k]);
if ($subDiff) $diff[$k] = $subDiff;
} elseif ($v !== $b[$k]) {
$diff[$k] = ['old' => $v, 'new' => $b[$k]]; // 修改
}
}
// 新增鍵
foreach ($b as $k => $v) {
if (!array_key_exists($k, $a)) {
$diff[$k] = ['old' => null, 'new' => $v];
}
}
return $diff;
}
print_r(arrayDiffRecursive($defaultConfig, $envConfig));
/*
[debug] => [old=>false, new=>true]
[db] => [
[host] => [old=>localhost, new=>db-server]
[port] => [old=>3306, new=>null] (刪除)
[user] => [old=>null, new=>root] (新增)
]
[allowed_ips] => [值差異...]
*/
?>
三、棧/隊列操作 (陣列模擬)
<?php
$stack = [1, 2, 3];
// 棧 (LIFO): 後進先出
array_push($stack, 4, 5); // [1,2,3,4,5] 返回新長度 5
$top = array_pop($stack); // 5, $stack=[1,2,3,4]
// 隊列 (FIFO): 先進先出
$queue = [1, 2, 3];
array_push($queue, 4); // 入隊: [1,2,3,4]
$front = array_shift($queue); // 出隊: 1, $queue=[2,3,4]
// 雙端隊列
array_unshift($queue, 0); // 頭部插入: [0,2,3,4]
$last = array_pop($queue); // 尾部移除: 4
// 效能注意:array_shift/unshift 需重新索引所有元素 O(n)
// 大量操作建議用 SplDoublyLinkedList 或 SplQueue
?>
SPL 專業資料結構
<?php
// SplStack (棧)
$stack = new SplStack();
$stack->push('a');
$stack->push('b');
echo $stack->pop(); // 'b'
echo $stack->top(); // 'a' (查看不移除)
// SplQueue (隊列)
$queue = new SplQueue();
$queue->enqueue('a');
$queue->enqueue('b');
echo $queue->dequeue(); // 'a'
// SplHeap / SplMaxHeap / SplMinHeap (優先隊列)
$heap = new SplMaxHeap();
$heap->insert(3);
$heap->insert(1);
$heap->insert(5);
while (!$heap->isEmpty()) {
echo $heap->extract(); // 5, 3, 1
}
// SplFixedArray (固定大小陣列,記憶體效率高)
$arr = new SplFixedArray(1000000);
$arr[0] = 'test';
// 比 array() 節省 ~30% 記憶體
?>
四、遞歸處理多維陣列
遞歸遍歷模板
<?php
function arrayWalkRecursive(array $array, callable $callback, int $depth = 0): array {
$result = [];
foreach ($array as $key => $value) {
if (is_array($value)) {
$result[$key] = arrayWalkRecursive($value, $callback, $depth + 1);
} else {
$result[$key] = $callback($value, $key, $depth);
}
}
return $result;
}
// 使用:所有字串修剪
$trimmed = arrayWalkRecursive($data, fn($v) => is_string($v) ? trim($v) : $v);
// 使用:標記深度
$withDepth = arrayWalkRecursive($data, fn($v, $k, $d) => ['value' => $v, 'depth' => $d]);
?>
扁平化多維陣列
<?php
function flattenArray(array $array, string $separator = '.'): array {
$result = [];
function _flatten(array $array, string $prefix, array &$result, string $separator) {
foreach ($array as $key => $value) {
$newKey = $prefix ? $prefix . $separator . $key : $key;
if (is_array($value)) {
_flatten($value, $newKey, $result, $separator);
} else {
$result[$newKey] = $value;
}
}
}
_flatten($array, '', $result, $separator);
return $result;
}
$nested = [
'user' => [
'name' => 'Alice',
'address' => ['city' => 'Taipei', 'zip' => '100']
],
'settings' => ['theme' => 'dark']
];
print_r(flattenArray($nested));
/*
[user.name] => Alice
[user.address.city] => Taipei
[user.address.zip] => 100
[settings.theme] => dark
*/
?>
五、陣列與生成器配合 (記憶體優化)
<?php
// 大數據集分塊處理
function chunkedArrayGenerator(iterable $source, int $chunkSize): Generator {
$chunk = [];
foreach ($source as $item) {
$chunk[] = $item;
if (count($chunk) >= $chunkSize) {
yield $chunk;
$chunk = [];
}
}
if ($chunk) yield $chunk;
}
// 使用:處理 100 萬條記錄,每 1000 條一批
foreach (chunkedArrayGenerator($largeDataset, 1000) as $batch) {
processBatch($batch); // 釋放上一批記憶體
}
// 惰性映射
function mapGenerator(iterable $source, callable $fn): Generator {
foreach ($source as $key => $value) {
yield $key => $fn($value, $key);
}
}
// 組合使用
$processed = mapGenerator(
chunkedArrayGenerator($data, 100),
fn($batch) => array_map('processItem', $batch)
);
?>
六、記憶體優化最佳實踐
| 場景 | 問題 | 優化方案 |
| 大陣列複製 | foreach 產生副本 | 用引用 &$v 或 SplFixedArray |
| 超大陣列排序 | 記憶體峰值高 | 分塊排序 + 歸併、或外部排序 |
| 頻繁 array_push/pop | 擴容/縮容開銷 | 預分配 SplFixedArray、或雙端隊列 |
| 多維陣列深層複製 | 遞歸複製慢 | 序列化/unserialize 或 var_export+eval |
| 陣列作為映射表 | 鍵名為長字串 | 用整數 ID 映射、或 SplObjectStorage |
大陣列去重優化
<?php
// 百萬級陣列去重
function fastUnique(array $array): array {
// 方法 1: array_unique (內建,但會保留鍵名)
return array_values(array_unique($array));
// 方法 2: array_flip 兩次 (更快,但值必須能作鍵名)
// return array_keys(array_flip($array));
// 方法 3: 手動哈希表 (最可控)
$seen = [];
$result = [];
foreach ($array as $value) {
$key = is_scalar($value) ? $value : serialize($value);
if (!isset($seen[$key])) {
$seen[$key] = true;
$result[] = $value;
}
}
return $result;
}
?>
七、實戰模式:配置管理、數據轉換、API 回應構建
配置合併模式
<?php
class Config {
private array $data = [];
public function load(array $config): self {
$this->data = $this->deepMerge($this->data, $config);
return $this;
}
public function get(string $path, $default = null) {
$keys = explode('.', $path);
$current = $this->data;
foreach ($keys as $key) {
if (!is_array($current) || !array_key_exists($key, $current)) {
return $default;
}
$current = $current[$key];
}
return $current;
}
private function deepMerge(array $a, array $b): array {
$result = $a;
foreach ($b as $k => $v) {
if (isset($result[$k]) && is_array($result[$k]) && is_array($v)) {
$result[$k] = $this->deepMerge($result[$k], $v);
} else {
$result[$k] = $v;
}
}
return $result;
}
}
$config = new Config();
$config->load(require 'config/default.php')
->load(require 'config/local.php')
->load($_ENV); // 環境變量最高優先級
echo $config->get('db.host', 'localhost');
?>
API 回應構建器
<?php
class ApiResponse {
private array $data = [];
private int $code = 200;
private string $message = '';
private array $meta = [];
public static function success($data = null, string $message = ''): self {
$resp = new self();
$resp->data = $data ?? [];
$resp->message = $message;
return $resp;
}
public static function error(int $code, string $message, $data = null): self {
$resp = new self();
$resp->code = $code;
$resp->message = $message;
$resp->data = $data ?? [];
return $resp;
}
public function withMeta(array $meta): self {
$this->meta = $meta;
return $this;
}
public function toArray(): array {
return array_filter([
'code' => $this->code,
'message' => $this->message,
'data' => $this->data,
'meta' => $this->meta ?: null
], fn($v) => $v !== null && $v !== '');
}
public function send(): void {
http_response_code($this->code);
header('Content-Type: application/json; charset=utf-8');
echo json_encode($this->toArray(), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
}
}
// 使用
ApiResponse::success($users, '獲取成功')
->withMeta(['total' => 100, 'page' => 1, 'per_page' => 20])
->send();
?>
八、常見陷阱總結
- foreach 引用殘留:`foreach ($arr as &$v) { ... } unset($v);` 必須 unset
- array_merge 數字鍵重新索引:字串鍵合併用 `+` 運算子
- in_array 嚴格模式:第三參數 `true` 避免 `'123' == 123` 誤判
- array_search 返回 0 或 false:必須用 `!== false` 判斷
- count() 遞歸模式:`COUNT_RECURSIVE` 計算所有子元素,非層數
- 空陣列排序:`sort([])` 返回 `true`,不報錯
- array_column 重複鍵:後者覆蓋前者,需配合 `array_combine` 保留所有
PHP 陣列功能極其豐富,掌握核心函數語義差異、效能特徵、記憶體行為,配合 SPL 專業結構與生成器模式,能應對從簡單業務到高併發大數據的各種場景。建立團隊統一的陣列操作規範與工具庫,能顯著提升代碼質量與開發效率。