[英]Is removing elements from an ArrayList or LinkedList while iterating through it with a for loop in Java bad? If so, why?
I was showing my code to someone and they said that it would cause undefined behavior.我正在向某人展示我的代码,他们说这会导致未定义的行为。 Being a Java programmer, that's not something I understand well.
作为一名 Java 程序员,这不是我理解的。 In the following code block I am iterating through
scenes
, which is an ArrayList, and removing elements from it.在下面的代码块中,我将遍历
scenes
,这是一个 ArrayList,并从中删除元素。
for(int i = 0; i < scenes.size() - 1; i++)
{
if(!(Double.valueOf(scenes.get(i + 1)) - Double.valueOf(scenes.get(i)) > 10))
{
scenes.remove(i + 1);
i--;
}
}
This compiles and doesn't throw an exception at runtime, but I'm still not sure if it's a programming no-no, why it's a programming no-no, and what is the right way to do it.这会编译并且不会在运行时引发异常,但我仍然不确定它是否是编程禁忌,为什么它是编程禁忌,以及正确的方法是什么。 I've heard about using
Iterator.remove()
and about just creating a whole new List
.我听说过使用
Iterator.remove()
以及创建一个全新的List
。
In an ArrayList
, removing an element from the middle of the list requires you to shift all of the elements with a higher index down by one.在
ArrayList
中,从列表中间删除一个元素需要您将所有具有较高索引的元素向下移动一个。 This is fine if you do it once (or a small number of times), but inefficient if you do it repeatedly.如果您执行一次(或少量),这很好,但如果您重复执行,则效率低下。
You don't really want to use an Iterator
for this either, because Iterator.remove()
suffers from the same issue.您也不想为此使用
Iterator
,因为Iterator.remove()
也遇到了同样的问题。
A better approach to this is to go through the list, moving the elements you want to keep to their new positions;更好的方法是通过列表 go ,将要保留的元素移动到新位置; and then just remove the tail of the list at the end:
然后在最后删除列表的尾部:
int dst = 0;
for (int src = 0; src < scenes.size(); ++dst) {
// You want to keep this element.
scenes.set(dst, scenes.get(src++));
// Now walk along the list until you find the element you want to keep.
while (src < scenes.size()
&& Double.parseDouble(scenes.get(src)) - Double.parseDouble(scenes.get(dst)) <= 10) {
// Increment the src pointer, so you won't keep the element.
++src;
}
}
// Remove the tail of the list in one go.
scenes.subList(dst, scenes.size()).clear();
(This "shift and clear" approach is what is used by ArrayList.removeIf
; you can't use that directly here because you can't inspect adjacent elements in the list, you only have access to the current element). (这种“移位和清除”方法是
ArrayList.removeIf
使用的方法;您不能在此处直接使用它,因为您无法检查列表中的相邻元素,您只能访问当前元素)。
You can take a similar approach which will also work efficiently with non-random access lists such as LinkedList
.您可以采用类似的方法,该方法也可以有效地处理诸如
LinkedList
之类的非随机访问列表。 You need to avoid repeatedly calling get
and set
, since these are eg O(size)
in the case of LinkedList
.您需要避免重复调用
get
和set
,因为在LinkedList
的情况下这些是O(size)
。
In that case, you would use ListIterator
instead of plain indexes:在这种情况下,您将使用
ListIterator
而不是普通索引:
ListIterator<String> dst = scenes.listIterator();
for (ListIterator<String> src = scenes.listIterator(); src.hasNext();) {
dst.next();
String curr = src.next();
dst.set(curr);
while (src.hasNext()
&& Double.parseDouble(src.next()) - Double.parseDouble(curr) <= 10) {}
}
scenes.subList(dst.nextIndex(), scenes.size()).clear();
Or something like this.或者类似的东西。 I've not tested it, and
ListIterator
is always pretty confusing to use.我没有测试过它,而且
ListIterator
使用起来总是很混乱。
This is straightforward and will work for either ArrayList or LinkedList:这很简单,适用于 ArrayList 或 LinkedList:
Iterator<String> iterator = list.iterator();
double current = 0;
double next;
boolean firstTime = true;
while (iterator.hasNext()) {
if (firstTime) {
current = Double.parseDouble(iterator.next());
firstTime = false;
} else {
next = Double.parseDouble(iterator.next());
if (next - current > 10) {
current = next;
} else {
iterator.remove();
}
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.