在for循环内使用Iterator从ArrayList中删除元素

时间:2019-06-20 21:33:53

标签: java arraylist iterator

我有一个对象的ArrayList,该对象的版本号为字段。我想对该ArrayList做一些工作,但是我只想要对象的最新版本。我当时正在考虑这样编码:

company/

这有效吗?我不知道我们最初处于for循环中的事实是否会在运行时破坏ArrayList的可变性。

4 个答案:

答案 0 :(得分:0)

不,这不起作用。 iter.remove()将导致out for循环失败,并显示ConcurrentModificationException

您可以使用索引为for的循环以及BitSet来跟踪要删除的内容,而不是这样做:

BitSet toRemove = new BitSet();
for (int m = 0; m < ObjectList.size(); ++m) {
  if (toRemove.get(m)) continue;
  ObjectVO myVO = ObjectList.get(m);

  for (int c = 0; c < ObjectList.size(); ++c) {
    if (toRemove.get(c)) continue;
    ObjectVO checkVO = ObjectList.get(c);

    if(myVO.getID().equals(checkVO.getID()) {
      //they are the same object ID.  Check the version number, remove it lower
      if(myVO.getVersion() > checkVO.getVersion()) {
          toRemove.set(c);
      }
    }
  }
}

这基本上是您的代码,但尚未删除。然后,您可以在之后浏览列表并将其删除:

int dst = 0;
for (int src = 0; src < ObjectList.size(); ++src) {
  if (!toRemove.get(src)) {
    ObjectList.set(dst++, ObjectList.get(src));
  }
}
ObjectList.subList(dst, ObjectList.size()).clear();

像这样使用BitSet的要点是,如果要从末尾以外的任何地方删除ArrayList,效率很低,因为它需要所有元素在“右边”移除的元素的位置会随机移动一个位置。设置/获取和清除的循环仅允许您将每个保留的元素移动一次。


但是,如果将列表元素按具有相同ID的事物进行分组,则可以比二次循环做得更好:那么您就不必继续检查整个列表:

BitSet toKeep = new BitSet();
IntStream.range(0, ObjectList.size())
    .mapToObj(a -> a)
    .collect(
        groupingBy(a -> ObjectList.get(a).getID(),
                   maxBy(comparingInt(a -> ObjectList.get(a).getVersion()))))
    .values()
    .forEach(a -> toKeep.set(a));

int dst = 0;
for (int src = 0; src < ObjectList.size(); ++src) {
  if (toKeep.get(src)) {
    ObjectList.set(dst++, ObjectList.get(src));
  }
}
ObjectList.subList(dst, ObjectList.size()).clear();

答案 1 :(得分:0)

假设您有存储空间,而不是执行O(N ^ 2)操作,则可以通过使用Map跟踪每个ID的最新版本来更有效地执行操作(O(N))。一遍跟踪每个ID的最新版本,第二遍删除不是最新的元素。

Map<Integer, Thing> newestById = new HashMap<>();

for (Thing thing : list) {
  newestById.merge(thing.id, thing, (a,b) -> a.version > b.version ? a : b);
}
list.removeIf(thing -> thing != newestById.get(thing.id)); }

根据您的用例,您甚至可以将数据存储在Map中而不​​是List中,并在将其添加到Map中之前检查版本是否为最新版本。

答案 2 :(得分:0)

其他答案已经讨论过,这是行不通的。正如我所看到的,您有三个选择,可以为CPU周期/灵活性交换内存。在我的示例中,我使用了Integer而不是ObjectVO,但是交换它们很简单。

选项1 -中等内存,阵列的单次通过

跟踪您看到的最高ID,并在符合条件的新项目中填充ArrayList。当遇到新的更高ID时,丢弃ArrayList并创建一个新ID:

ArrayList<Integer> objectList = getObjectList();
Integer bestId = -1;
ArrayList<Integer> allObjectsMatchingId = new ArrayList<>();
for(Integer currentObject : objectList) {
    if(currentObject > bestId) {
        bestId = currentObject;
        allObjectsMatchingId = new ArrayList<>();
    } else if(currentObject == bestId) {
        allObjectsMatchingId.add(currentObject);
    }
}
return allObjectsMatchingId;

选项2 -内存更昂贵,阵列的单次通过,最灵活。

对于您看到的每个ID,创建一个ArrayList并将其存储在地图上。这样一来,您就可以轻松更改有关要保留的ID的标准。

ArrayList<Integer> objectList = getObjectList();
Map<Integer, ArrayList<Integer>> objectsById = new HashMap<>();
for(Integer currentObject : objectList) {
    ArrayList<Integer> listForId = objectsById.get(currentObject);
    if(listForId == null) {
        listForId = new ArrayList<Integer>();
    }
    listForId.add(currentObject);
    objectsById.put(currentObject, listForId);
}
Integer bestId = -1;
for(Integer i : objectsById.keySet()) {
    if(i > bestId) {
        bestId = i;
    }
}
return objectsById.get(bestId);

选项3 -除id,数组的两次遍历外,没有其他内存。

在ArrayList中搜索最高ID,然后将数组过滤为仅通过该过滤器的元素。

这是最接近当前实现的方法,不同之处在于您需要在单独的步骤中进行操作。这样可以将复杂度从O(N ^ 2)降低到O(N),这是有效的,因为您在迭代时不修改ArrayList。如果您与Java 8兼容,则可以在此处使用Stream而不是迭代器进行过滤。参见Java: Efficient ArrayList filtering?

ArrayList<Integer> objectList = getObjectList();
Integer bestId = -1;
for(Integer currentObject : objectList) {
    if(currentObject > bestId) {
        bestId = currentObject;
    }
}
Iterator<Integer> iter = objectList.iterator();
while(iter.hasNext()) {
    if(iter.next() != bestId) {
        iter.remove();
    }
}

答案 3 :(得分:0)

为什么不使用Java Streams解决此问题:

Collection<ObjectVO> result = objectList.stream()
        .collect(Collectors.toMap(ObjectVO::getID, Function.identity(),
                BinaryOperator.maxBy(Comparator.comparing(ObjectVO::getVersion))))
        .values();

这将创建一个包含每个ID的最高版本的地图。然后,您可以只使用Map.values()来获取对象列表。

如果您需要ListArrayList,则可以只使用new ArrayList<>(result)