unset() 是 PHP 刪除變數/陣列元素的核心語言結構。看似簡單,實則涉及索引重組、引用計數、foreach 迭代器失效等進階機制。本文全面解析其行為細節與最佳實踐。
$arr = ['朝陽區', '海澱區', '西城區', '東城區', '豐台區'];
// 索引:0, 1, 2, 3, 4
unset($arr[3]); // 刪除 '東城區'
print_r($arr);
/*
Array
(
[0] => 朝陽區
[1] => 海澱區
[2] => 西城區
[4] => 豐台區 ← 索引 3 消失,索引 4 保留,不重組!
)
*/
核心特徵: unset() 刪除陣列元素後,數值索引不重組,原索引留空,陣列變為「稀疏陣列」。
| 方法 | 程式碼 | 特點 |
|---|---|---|
array_values() | $arr = array_values($arr); | 重新建立 0 起始連續索引,最常用 |
array_merge() | $arr = array_merge($arr); | 同 array_values,單參數時重組索引 |
unset + 重賦值 | unset($arr[3]); $arr = array_values($arr); | 標準兩步驟 |
array_splice() | array_splice($arr, 3, 1); | 原地刪除並重組索引,返回被刪元素 |
$a = '大字串內容...'; // 1MB
$b = $a; // 同一記憶體,引用計數=2
$c = $a; // 引用計數=3
unset($a); // 引用計數=2,記憶體未釋放
unset($b); // 引用計數=1,記憶體未釋放
unset($c); // 引用計數=0,記憶體釋放
// 陣列情況
$arr = [1,2,3,4,5];
$ref = &$arr[2]; // 建立引用
unset($arr[2]); // 刪除陣列元素,但 $ref 仍持有值 3
echo $ref; // 3
unset($ref); // 徹底釋放
關鍵點:PHP 使用引用計數管理記憶體。unset() 只是減少引用計數,當計數歸零時才真正釋放記憶體。陣列元素被 unset 但被其他變數引用時,值仍存活。
$arr = [1, 2, 3, 4, 5];
foreach ($arr as $key => $value) {
if ($value === 3) {
unset($arr[$key]); // 刪除的是副本,原陣列不受影響!
}
}
print_r($arr); // [1,2,3,4,5] 未刪除
$arr = [1, 2, 3, 4, 5];
foreach ($arr as $key => &$value) {
if ($value === 3) {
unset($arr[$key]); // 危險!迭代器指向已釋放記憶體
}
}
// 後續迭代行為未定義,可能導致段錯誤或跳過元素
$arr = [1, 2, 3, 4, 5];
$toDelete = [];
foreach ($arr as $key => $value) {
if ($value === 3 || $value === 4) {
$toDelete[] = $key;
}
}
foreach ($toDelete as $key) {
unset($arr[$key]);
}
// 或
// array_splice($arr, 2, 2); // 知道位置時更高效
print_r($arr); // [1, 2, 5]
$arr = [1, 2, 3, 4, 5];
$arr = array_filter($arr, fn($v) => $v !== 3 && $v !== 4);
$arr = array_values($arr); // 重組索引
// [1, 2, 5]
優勢:函數式風格、無副作用、自動重組索引、程式碼簡潔。
$data = [
'users' => [
['id' => 1, 'name' => 'A'],
['id' => 2, 'name' => 'B'],
['id' => 3, 'name' => 'C'],
]
];
// 刪除 id=2 的用戶
$data['users'] = array_values(array_filter($data['users'], fn($u) => $u['id'] !== 2));
// 或使用引用遞迴刪除
function unsetDeep(array &$array, callable $condition): void
{
foreach ($array as $key => &$value) {
if (is_array($value)) {
unsetDeep($value, $condition);
if (empty($value)) {
unset($array[$key]);
}
} elseif ($condition($value, $key)) {
unset($array[$key]);
}
}
unset($value);
}
<?php
$arr = range(1, 100000);
$start = hrtime(true);
foreach ($arr as $k => $v) {
if ($v % 2 === 0) unset($arr[$k]);
}
$arr = array_values($arr);
echo "unset+values: " . (hrtime(true)-$start)/1e6 . " ms\n";
$arr = range(1, 100000);
$start = hrtime(true);
$arr = array_filter($arr, fn($v) => $v % 2 !== 0);
echo "array_filter: " . (hrtime(true)-$start)/1e6 . " ms\n";
$arr = range(1, 100000);
$start = hrtime(true);
$del = [];
foreach ($arr as $k => $v) if ($v % 2 === 0) $del[] = $k;
foreach ($del as $k) unset($arr[$k]);
$arr = array_values($arr);
echo "collect+unset: " . (hrtime(true)-$start)/1e6 . " ms\n";
典型結果: array_filter 通常最快(C 層實現),其次收集鍵名法,unset 邊遍歷邊刪除最慢(頻繁 HashTable 重組)。
unset($arr[$key]) + 需要時 array_values()array_filter() + array_values()(首選)array_splice($arr, $offset, $length)unset($ref) 再 unset($arr[$key])掌握 unset() 的索引保留特性與引用計數機制,能有效避免「以為刪除了實際沒刪除」「索引亂序導致邏輯錯誤」「foreach 迭代器失效」等經典坑點,編寫更健壯的陣列操作代碼。