foreach 会跳过下一个元素,因其内部指针按哈希表物理顺序前进,unset 后桶被标记删除但位置不前移,指针直接跳到下一物理位置,导致紧邻的有效元素被跳过。

会跳过部分元素。这是 PHP foreach 内部实现机制导致的确定性行为,不是偶然或版本差异。
为什么 foreach 会跳过下一个元素
PHP 的 foreach 在开始遍历时会创建一个内部数组指针(不是用户可见的 current/next 指针),并按当前哈希表(HashTable)的物理顺序逐个读取元素。当你在循环中执行 unset($arr[$key]) 时:
- 被删元素的桶(bucket)被标记为“已删除”,但后续元素在哈希表中的位置不会自动前移;
- 内部指针照常前进到“下一个物理位置”;
- 如果被删元素后面紧挨着一个有效元素,而该位置因删除操作被腾空或重排,指针就可能直接跨过它,导致跳过。
尤其在连续数字索引数组中,这种跳过现象更明显。例如:[0=>'a', 1=>'b', 2=>'c'],在处理 $key=1 时 unset($arr[1]),指针从位置 1 跳到位置 2,但原位置 2 的元素可能已被移动或覆盖,结果 'c' 就被跳过了。
哪些操作会触发跳过
以下写法都存在跳过风险:
foreach ($arr as $key => $value) { if (condition) unset($arr[$key]); }foreach ($arr as &$v) { if (condition) unset($arr[array_search($v, $arr)]); }- 哪怕只删一个元素,只要发生在循环体内,就可能影响后续迭代逻辑。
安全删除的推荐做法
避免在 foreach 中直接 unset,改用以下方式之一:
- 先收集要删的键名:$keys = array_keys($arr, 'target'); foreach ($keys as $k) unset($arr[$k]);
- 倒序 for 循环(仅适用于数字索引连续数组):for ($i = count($arr)-1; $i >= 0; $i--) { if (condition) unset($arr[$i]); }
- 用
array_filter()重构数组:$arr = array_filter($arr, fn($v) => $v !== 'target');
其中 array_filter 最语义清晰、无副作用,适合大多数过滤场景;若需保留原始键且只删少量项,收集键名再批量 unset 效率更高。
补充说明:引用赋值不能解决跳过问题
即使写成 foreach ($arr as $key => &$v),也不能规避跳过。引用只影响值的修改,不改变底层哈希表遍历逻辑。unset 操作依然作用于原数组结构,指针错位问题照旧。

















