【问题标题】:Removing items from a list while iterating over it在迭代列表时从列表中删除项目
【发布时间】:2020-05-27 01:09:26
【问题描述】:

我已经看到了很多关于这个主题的问题,但没有一个答案真的很适合我的用例(除非我解释错了。)。是否可以在迭代器迭代时从列表中删除项目?

我想要实现的是有一个带有音频播放器的队列。迭代器在播放歌曲时遍历队列并阻塞。歌曲在播放时可以添加到队列中或从队列中删除。

我已经尝试了上述想法并收到了异常 ConcurrentModificationException 。 我还读到,在迭代器对其进行迭代时对集合进行变异是一种不好的做法。我希望有人可以为我指出正确的方向,即如何在迭代它时从方法调用中正确地改变列表。

【问题讨论】:

  • 这能回答你的问题吗? Remove elements from collection while iterating
  • 您可以使用临时列表并在迭代时仅添加所需的值,然后将此临时列表分配给原始列表
  • @Adnan 但是迭代器在到达它之前不知道是否需要该值。并且只有在请求或“音频”播放完毕时才会转到下一个/上一个。因此,在迭代器到达“标记为删除”的对象之前,它仍然在列表中。
  • 我建议将其编写为自定义类,而不实现 IterableIterator。将歌曲存储在 ArrayList 中,并跟踪当前播放歌曲的索引;要获得下一个,请增加该索引并在新索引处返回歌曲。添加或删除歌曲时,相应地更新索引。使用Iterable/Iterator 太复杂了,因为它是知道如何更新索引的主类,而不是迭代器本身;并且您似乎不需要在同一个列表上拥有多个并发迭代器。
  • @kaya3 我已经在写a similar approach,但是感谢AbstractList,它通过一些方法重新路由了所有方法和功能,支持整个 Collection API 并且仍然可以更新索引正确。

标签: java collections iterator concurrentmodification


【解决方案1】:

最好的解决方案是不保持当前播放的歌曲。下一首通过Iterator 播放的歌曲。相反,您可以创建一个专门的列表,该列表知道如何根据修改调整此指针。

这样的类可能看起来像

class SongList extends AbstractList<Song> implements RandomAccess {
    final List<Song> backend = new ArrayList<>();
    int currentSong = -1;

    SongList() {}
    SongList(Collection<? extends Song> c) {
        backend.addAll(c);
    }
    // mandatory query methods

    @Override public int size() {
        return backend.size();
    }
    @Override public Song get(int index) {
        return backend.get(index);
    }

    // the "iterator"
    public Song nextSong() {
        if(++currentSong < size()) {
            return get(currentSong);
        }
        currentSong = -1;
        return null;
    }

    // modifying methods, which will adapt the pointer

    @Override public void add(int index, Song element) {
        backend.add(index, element);
        if(index <= currentSong) currentSong++;
    }
    @Override public Song remove(int index) {
        final Song removed = backend.remove(index);
        if(index <= currentSong) currentSong--;
        return removed;
    }

    @Override
    public boolean addAll(int index, Collection<? extends Song> c) {
        int old = size();
        backend.addAll(index, c);
        if(index <= currentSong) currentSong += size() - old;
        return true;
    }

    @Override protected void removeRange(int fromIndex, int toIndex) {
        backend.subList(fromIndex, toIndex).clear();
        if(fromIndex <= currentSong)
            currentSong = Math.max(fromIndex - 1, currentSong - toIndex + fromIndex);
    }

    // this will not change the pointer

    @Override public Song set(int index, Song element) {
        return backend.set(index, element);
    }

    // query methods overridden for performance

    @Override public boolean contains(Object o) {
        return backend.contains(o);
    }
    @Override public int indexOf(Object o) {
        return backend.indexOf(o);
    }
    @Override public Spliterator<Song> spliterator() {
        return backend.spliterator();
    }
    @Override public void forEach(Consumer<? super Song> action) {
        backend.forEach(action);
    }
    @Override public Object[] toArray() {
        return backend.toArray();
    }
    @Override public <T> T[] toArray(T[] a) {
        return backend.toArray(a);
    }
    @Override public String toString() {
        return backend.toString();
    }
}

AbstractList 专门设计用于在几个方法之上提供集合操作,因此我们只需要实现size()get(int) 即可获得可读列表并提供add(int, Song)remove(int) 和@ 987654328@ 我们已经完成了支持所有修改操作所需的一切。提供其他方法只是为了提高性能,继承的方法也可以。

列表支持指向当前播放位置的单个指针,可以通过nextSong() 进行迭代。当到达结束时,它会返回null并重置指针,这样下一个查询将重新开始。 addremove 方法将调整指针,以便不再播放已播放的歌曲(除非重新启动整个列表)。

基于set的修改不适应指针,这意味着当你sort列表时不会发生任何有意义的事情,一些策略是可以想象的,但至少当列表有重复时,不存在完美的行为。与其他播放器软件相比,似乎没有人期望在播放时将列表颠倒过来会有完美的表现。至少,永远不会有例外。

【讨论】:

  • 结合我今天早些时候画的内容,这为我指明了正确的方向。谢谢!
【解决方案2】:

使用Iterator 并调用其remove() 方法:

List<String> myList = new ArrayList<>();
for (Iterator<String> i = myList.iterator(); i.hasNext();) {
    String next = i.next();
    if (some condition) {
        i.remove(); // removes the current element
    }
}

【讨论】:

  • 需要注意的是,并非所有列表的迭代器都支持删除。
  • 我想从方法调用中编辑列表,而不是迭代器本身。迭代器在处理完当前对象后应该只获取上一个/下一个对象。
猜你喜欢
  • 2014-10-26
  • 1970-01-01
  • 1970-01-01
  • 2022-05-17
  • 2011-09-23
  • 1970-01-01
  • 2011-11-26
  • 2011-03-18
相关资源
最近更新 更多