简体   繁体   English

使用迭代器从 Java 集合中删除元素

[英]Using Iterators to remove elements from a Java Collection

There are many posts that suggest using Iterators to safely remove an element from a collection.有很多帖子建议使用迭代器从集合中安全地删除元素。 Something like this:像这样的东西:

Iterator<Book> i = books.iterator();
while(i.hasNext()){
    if(i.next().isbn().equals(isbn)){
        i.remove();
    }
}

According to the documentation, the benefit of using an Iterator is that it is "fail fast" in the sense that if any thread is modifying the collection (books in the above example), while the iterator is used, then the iterator would throw a ConcurrentModificationException.根据文档,使用迭代器的好处是它是“快速失败”的,因为如果任何线程正在修改集合(上面示例中的书籍),而使用迭代器,那么迭代器将抛出一个并发修改异常。 However, the documentation of this exception also says但是,此异常的文档也说

Note that fail-fast behavior cannot be guaranteed as it is, generally speaking, impossible to make any hard guarantees in the presence of unsynchronized concurrent modification.请注意,不能保证快速失败的行为,因为一般来说,在存在不同步的并发修改的情况下,不可能做出任何硬保证。 Fail-fast operations throw ConcurrentModificationException on a best-effort basis.快速失败操作会尽最大努力抛出 ConcurrentModificationException。 Therefore, it would be wrong to write a program that depended on this exception for its correctness: ConcurrentModificationException should be used only to detect bugs.因此,编写一个依赖此异常来确保其正确性的程序是错误的:ConcurrentModificationException 应该仅用于检测错误。

Does this mean that using iterators is not an option if 100% correctness has to be guaranteed?这是否意味着如果必须保证 100% 的正确性,则不能选择使用迭代器? Do I need to design my code in such a way that removal while the collection is modified would always result in correct behavior?我是否需要以这样一种方式设计我的代码,即在修改集合时删除总是会导致正确的行为? If so, can anyone give an example where using the.remove() method of an iterator is useful outside of testing?如果是这样,谁能举例说明使用迭代器的 .remove() 方法在测试之外很有用?

Iterator.remove will work as long as no other thread changes the Collection while you're iterating over it.只要您在迭代集合时没有其他线程更改集合, Iterator.remove就可以工作。 Sometimes its a handy feature.有时它是一个方便的功能。

When it comes to multithreaded environment, it really depends on how do you organize the code.当涉及到多线程环境时,这实际上取决于您如何组织代码。 For example if you create a collection inside a web request and do not share it with other requests (for example if it gets passed to some methods via method parameters) you can still safely use this method of traversing the collection.例如,如果您在 web 请求中创建一个集合并且不与其他请求共享它(例如,如果它通过方法参数传递给某些方法),您仍然可以安全地使用这种遍历集合的方法。

On the other hand, if you have say a 'global' queue of metrics snapshots shared among all the requests, each request adds stats to this queue, and some other thread reads the queue elements and deletes the metrics, this way won't be appropriate.另一方面,如果您说在所有请求之间共享一个“全局”指标快照队列,每个请求都会向该队列添加统计信息,并且其他一些线程读取队列元素并删除指标,这种方式不会合适的。 So its all about the use case and the how do you organize the code.因此,这一切都与用例以及如何组织代码有关。

As for the example that you're asking for, say you have a collection of Strings and would like to remove all the strings that start with a letter 'a' by modifying the existing collection至于您要求的示例,假设您有一个字符串集合,并希望通过修改现有集合来删除所有以字母“a”开头的字符串

Iterator<String> i = strings.iterator();
while(i.hasNext()){
    if(i.next().startsWith('a')){
        i.remove();
    }
}

Of course in Java 8+ you can achieve almost the same with Streams:当然,在 Java 8+ 中,您可以使用 Streams 实现几乎相同的效果:

strings.stream()
.filter(s -> !s.startsWith('a'))
.collect(Collectors.toList());

However, this method creates a new collection, rather than modifying the existing one (like in the case with iterators).但是,此方法创建一个新集合,而不是修改现有集合(如使用迭代器的情况)。

In pre java 8 world (and iterators have appeared way before java 8 was available), we don't even have streams, so code like this was not really straightforward task to write.在 java 8 之前的世界中(迭代器在 java 8 可用之前就已经出现了),我们甚至没有流,所以这样的代码并不是很容易编写的任务。

Iterator#remove guarantees 100% correctness for single-threaded processing. Iterator#remove保证单线程处理的 100% 正确性。 In multi-threaded processing of data, it depends on how (synchronized/asynchronized processing, using a different list for collecting the elements to be removed etc.) you process the data.在数据的多线程处理中,它取决于您如何处理数据(同步/异步处理,使用不同的列表来收集要删除的元素等)。

As long as you do not want the same collection to be modified, you can collect the elements to be removed, into a separate List and use List#removeAll(Collection<?> c) as shown below:只要你不想修改同一个集合,你可以将要移除的元素收集到一个单独的List中,然后使用List#removeAll(Collection<?> c)如下所示:

import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);

        List<Integer> elementsToBeRemoved = new ArrayList<>();

        for (Integer i : list) {
            if (i % 2 == 0) {
                elementsToBeRemoved.add(i);
            }
        }

        list.removeAll(elementsToBeRemoved);

        System.out.println(list);
    }
}

Output: Output:

[1, 3]

In a loop, never remove elements using the index在循环中,永远不要使用索引删除元素

For a beginner, it may be tempting to use List#remove(int index) to remove the elements using index but the fact that every remove operation resizes the List makes it produce confusing results eg对于初学者来说,使用List#remove(int index)来删除使用 index 的元素可能很诱人,但是每个删除操作都会调整List的大小这一事实使其产生令人困惑的结果,例如

import java.util.Iterator;
import java.util.List;
import java.util.Vector;

public class Main {
    public static void main(String[] args) {
        List<Integer> list = new Vector<>();
        list.add(1);
        list.add(2);
        Iterator<Integer> i = list.iterator();
        while (i.hasNext()) {
            System.out.println("I'm inside the iterator loop.");
            i.next();
            list.remove(0);
        }

        System.out.println(list);
    }
}

Output: Output:

I'm inside the iterator loop.
[2]

The reason for this output is depicted below:此 output 的原因如下所示:

在此处输入图像描述

Here is an interesting piece of code (could be a good interview question).这是一段有趣的代码(可能是一个很好的面试问题)。 Would this program compile?这个程序会编译吗? And if so, would it run without exceptions?如果是这样,它会毫无例外地运行吗?

List<Integer> list = new Vector<>();
list.add(1);
list.add(2);
Iterator<Integer> i = list.iterator();
while (i.hasNext()) {
    i.next();
    list.remove(0);
}

Answer: yes.回答:是的。 It would compile and run without exceptions.它将毫无例外地编译和运行。 That's because there are two remove methods for the list:那是因为列表有两种删除方法:

E remove(int index) Removes the element at the specified position in this list (optional operation). E remove(int index)删除此列表中指定 position 处的元素(可选操作)。

boolean remove(Object o) Removes the first occurrence of the specified element from this list, if it is present (optional operation). boolean remove(Object o)如果指定元素存在,则从该列表中删除第一个出现的元素(可选操作)。

And the one that gets called is boolean remove(Object o) .而被调用的是boolean remove(Object o) Since 0 is not in the list, the list is not modified, and there is no error.由于 0 不在列表中,所以列表没有被修改,也没有错误。 This doesn't mean that there's something wrong with the concept of an iterator, but it shows that, even in a single thread situation, just because an iterator is used, does not mean the developer cannot make mistakes.这并不意味着迭代器的概念有问题,但它表明,即使在单线程情况下,仅仅因为使用了迭代器,并不意味着开发人员不会出错。

Does this mean that using iterators is not an option if 100% correctness has to be guaranteed?这是否意味着如果必须保证 100% 的正确性,则不能选择使用迭代器?

Not necessarily.不必要。

First of all, it depends on your criteria for correctness.首先,这取决于您的正确性标准。 Correctness can only be measured against specified requirements.正确性只能根据指定的要求来衡量。 Saying something is 100% correct is meaningless if you don't say what the requirements are.如果您不说出要求是什么,那么说某事是 100% 正确是没有意义的。

There are also some generalizations that we can make.我们也可以做出一些概括。

  1. If a collection (and its iterator) is used by one thread only, 100% correctness can be guaranteed.如果一个集合(及其迭代器)仅由一个线程使用,则可以保证 100% 的正确性。

  2. A concurrent collection types can be safely accessed and updated via its iterators from any number of threads.可以从任意数量的线程通过其迭代器安全地访问和更新并发集合类型。 There are some caveats though:不过有一些注意事项:

    • An iteration is not guaranteed to see structural changes made after the iteration starts.不保证迭代在迭代开始后会看到结构更改。
    • An iterator is not designed to be shared by multiple threads.迭代器并非设计为由多个线程共享。
    • Bulk operations on a ConcurrentHashMap are not atomic. ConcurrentHashMap上的批量操作不是原子的。

    If your correctness criteria do not depend one these things, then 100% correctness can be guaranteed.如果您的正确性标准不依赖于这些东西,那么可以保证 100% 的正确性。

Note: I'm not saying that iterators guarantee correctness.注意:我并不是说迭代器保证正确性。 I am saying that iterators can be part of a correct solution, assuming that you use them the right way.我是说迭代器可以成为正确解决方案的一部分,前提是您以正确的方式使用它们。

Do I need to design my code in such a way that removal while the collection is modified would always result in correct behavior?我是否需要以这样一种方式设计我的代码,即在修改集合时删除总是会导致正确的行为?

It depends how you use the collection.这取决于您如何使用该集合。 See above.看上面。

But as a general rule, you do need to design and implement you code to be correct.但作为一般规则,您确实需要设计实现代码才能正确。 (Correctness won't happen by magic...) (正确性不会靠魔法发生……)

If so, can anyone give an example where using the remove() method of an iterator is useful outside of testing?如果是这样,任何人都可以举一个例子说明使用迭代器的remove()方法在测试之外是有用的吗?

In any example where only one thread can access the collection, using remove() is 100% safe, for all standard collection classes.在任何只有一个线程可以访问集合的示例中,对于所有标准集合类,使用remove()是 100% 安全的。

In many examples where the collection is a concurrent type, remove() is 100% safe.在集合是并发类型的许多示例中, remove()是 100% 安全的。 (But there is no guarantee that an element will stay removed if another thread is simultaneously trying to add it. Or that it will be added for that matter.) (但不能保证如果另一个线程同时尝试添加一个元素,它会保持被删除。或者它会因此而被添加。)

The bottom line is that if your application is multi-threaded, then you have to understand how different threads may interact with shared collections.底线是,如果您的应用程序是多线程的,那么您必须了解不同的线程如何与共享的 collections 交互。 There is no way to avoid that.没有办法避免这种情况。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 如何在java中使用嵌套迭代器从LinkedList中删除元素 - How to remove elements from LinkedList with nested iterators in java 如果HashSet,ArrayList返回的迭代器是快速失败的,我们如何使用迭代器remove()从Collection中删除元素 - If Iterators returned by HashSet, ArrayList are fail-fast, how can we use iterator remove() to remove elements from Collection 从集合中删除元素 - Remove elements from collection 使用迭代器从Java中的List中删除重复项 - Removing duplicates from a List in Java using iterators Java:如何将集合传递给chainedIterator(集合<iterator<? extends e> &gt; 迭代器)来自 Apache commons collection4 lib? </iterator<?> - Java : How to pass collection to chainedIterator(Collection<Iterator<? extends E>> iterators) from Apache commons collection4 lib? 使用 java 8 从比较元素的列表中删除 - Remove from list comparing elements using java 8 从Java的背面进行迭代时,为迭代器实现remove() - Implement remove() for iterators when iterating from the back in Java Java中的迭代器-删除范围内的数字 - Iterators in java - remove numbers in a range Java:如果集合元素对特定超时处于非活动状态,则删除它们 - Java: remove collection elements if they are inactive for specific timeout 两个java.util.Iterators到同一个集合:他们必须以相同的顺序返回元素吗? - Two java.util.Iterators to the same collection: do they have to return elements in the same order?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM