ArrayObject::offsetUnset意外推进内部指针问题排查
为啥遍历ArrayObject时移除元素,会输出0 1 2 4?
嘿,这个问题我之前踩过坑,咱们来唠唠到底咋回事~
先把你的代码贴出来,方便大家看:
$col = new ArrayObject(); for ($i = 0; $i < 5; $i++) { $objItem = new stdClass; $objItem->someVar = $i; $col->append($objItem); } foreach($col AS $key => $objItem) { if ($key == 2) { $col->offsetUnset($key); } echo $key.' '; } // 意外输出:0 1 2 4
问题根源:ArrayObject的迭代器特性
PHP里的foreach遍历ArrayObject时,用的是它自带的ArrayIterator迭代器。和普通数组遍历不同,普通数组是先复制一份再遍历,而ArrayIterator是实时访问原对象的元素,但迭代的指针逻辑在删除元素时会“踩空”:
- 初始时,
$col里的元素键是0、1、2、3、4,迭代器按顺序逐个访问。 - 迭代到
key=2时,你调用offsetUnset(2)把这个元素删了。此时$col里剩下的元素键变成0、1、3、4,顺序是0→1→3→4。 - 循环体执行完,迭代器要调用
next()跳到下一个元素。但因为刚才删了当前位置的元素,迭代器的指针直接跳过了原本的key=3(现在它的位置往前挪了一位),直接指向了原来的key=4,所以你就看不到3的输出,反而看到了4。
怎么解决?给你三个靠谱方案
方案1:先拿键的副本再遍历
先把ArrayObject的键转成普通数组,遍历这个键数组再操作原对象,这样就不会受实时修改的影响:
$keys = array_keys($col->getArrayCopy()); foreach($keys as $key) { if ($key == 2) { $col->offsetUnset($key); } echo $key.' '; } // 输出:0 1 2 3 4,同时成功删除key=2的元素
方案2:反向遍历删元素
从最后一个元素往前遍历,删除元素不会影响前面的迭代逻辑:
for ($key = $col->count() - 1; $key >= 0; $key--) { if ($key == 2) { $col->offsetUnset($key); } echo $key.' '; } // 输出:4 3 2 1 0,也能正确删除目标元素
方案3:用迭代器的removeCurrent()方法
直接用ArrayIterator的removeCurrent()方法删除当前元素,这是最规范的做法,不会出现指针跳步的问题:
$iterator = $col->getIterator(); while($iterator->valid()) { $key = $iterator->key(); if ($key == 2) { $iterator->removeCurrent(); } else { echo $key.' '; } $iterator->next(); } // 输出:0 1 3 4,完美跳过被删除的元素,遍历逻辑完全正常
内容的提问来源于stack exchange,提问作者Atural
相关产品推荐
相关产品推荐

