在Java中从ArrayList中删除对象

32

我需要从一个 ArrayList 中删除一些对象,如果它们满足某个条件,我想知道哪种方式更有效率。

这是情况:我有一个包含其他对象的 ArrayList 的类。我必须遍历这个 ArrayList 并删除所有符合某个条件的元素。 据我所知,以下是我删除的选项:

  1. 创建一个新的 ArrayList 并添加不满足条件的元素。在迭代结束后,将旧的数组列表与没有元素的新列表进行交换。

  2. 创建一个新的 ArrayList 并添加符合条件的元素。在迭代结束后,使用 removeAll() 方法传递要删除的对象的 ArrayList

是否有更有效率的方法从 ArrayList 中删除对象?


2
除非您确信性能在代码的这个特定点上是一个问题,否则我建议忽略效率。还有一些其他事情需要考虑,例如:您是否在其他地方保留对原始列表的引用,以便应该反映更改?那么您不能使用1。并且您能否使用ArrayList.remove(),即equals()的语义是否按照您在列表中需要的方式工作? - Hanno Fietz
嗯,我所说的对象包含一些ArrayLists,我必须对它们所有进行相同的操作。我不知道这是否会成为瓶颈(我还没有测试过),但我想知道你们如何删除项目以查看是否有更好的选项。回答你的第二个问题:是的,我可以使用remove()方法。 - Carlos Pastor
13个回答

46

您可以反向迭代ArrayList并在遍历过程中进行删除。这样做的优点是后续元素不需要移动,比正向移动更容易编程。


不错,+1 :) 不知道那个性能提升是否真的能够察觉。 - Hanno Fietz
在某些情况下,是的-如果您担心的话,可能是需要进行分析的事情之一。通常我采取构建另一个要删除的项目列表的策略-这样其他程序员更容易理解。 - RichardOD
谢谢,Richard,这是一个我从未想过的有趣方法!+1 - Carlos Pastor
是的,这是满足需求的绝佳方式,即使在其他编程语言中也是常见的方法。+1。 - Decker
1
无论您是从列表的头部还是尾部遍历来找到该元素-其实际删除一定涉及将其右侧的其他成员向左移动。就实际删除而言,您的解决方案对我来说毫无意义。仅在您知道该元素位于列表末尾某处时,反向迭代才有益于搜索该元素。否则,您的解决方案与正向迭代没有区别,因为在删除时进行相同量的移位。 - Dhruv Gairola

16

另一种方法:迭代器(Iterator)有一个可选的remove()方法,它被实现于ArrayList中。在迭代过程中可以使用它。

然而我不确定哪种方式性能更好,您需要进行测试。

starblue提到复杂度不好,这是正确的(对于removeAll()也是如此),因为如果在中间添加或删除元素,ArrayList必须复制所有元素。在这种情况下,使用LinkedList更好。但是,由于我们都不知道您的具体用例,最好的方法是测试所有变体,以选择最佳解决方案。


然而,remove() 有一个注意点:它可能无法删除您当前查看的对象。引用 JavaDoc 中的话:“更正式地说,删除具有最低索引 i 的元素,使得 (o==null ? get(i)==null : o.equals(get(i)))(如果存在这样的元素)。” 因此,根据列表中的对象,它可能无法按预期工作。基本上,如果您可以将 ArrayList 替换为 SortedSet 而不损害应用程序,则应该没问题。 - Hanno Fietz
3
这是我在 Iterator 的 remove() 方法的 javadoc 中看到的内容:“从底层集合中删除迭代器返回的最后一个元素(可选操作)。每次调用 next 只能调用此方法一次。如果在迭代正在进行时以任何方式修改底层集合而不是通过调用此方法,则迭代器的行为是未指定的。” 你有什么疑问? - Buhb
2
这比问题中的两个提案更糟糕,因为它会导致O(n²)的运行时间。 - starblue
这也很糟糕,因为如果在迭代列表时抛出异常,那么列表将处于不一致的状态。如果列表不是方法本地的,则可能会出现问题。 - pjp
pjp: 对于 removeAll 方法和大多数非平凡的变化,这也是正确的。 starblue:O(n ^ 2)不一定慢。如果删除的数量相对较小,则可以快速执行。 - Tom Hawtin - tackline
显示剩余2条评论

12

我想最高效的方法应该是使用listIterator方法进行反向迭代:

for (ListIterator<E> iter = list.listIterator(list.size()); iter.hasPrevious();){
    if (weWantToDelete(iter.previous()))  iter.remove();
}

编辑: 很久以后,你可能还想使用lambda表达式或方法引用来从列表(或任何集合!)中删除元素,这是Java 8中删除元素的方式。如果您喜欢,可以对集合进行原地filter

list.removeIf(e -> e.isBad() && e.shouldGoAway());

这可能是清理集合的最佳方法,由于使用了内部迭代,所以集合实现可以采取捷径使其尽可能快(对于ArrayList,可以最小化所需的复制量)。


5

显然,你提到的两种方法中,第一种更加高效,因为它只需要遍历一次列表,而第二种方法需要遍历两次(首先查找要删除的元素,然后再删除它们)。

实际上,从另一个列表中删除一系列元素可能是一个O(n)算法,因此第二种方法甚至更差。

迭代器方法:

List data = ...;

for (Iterator i = data.iterator(); i.hasNext(); ) {
    Object element = i.next();

    if (!(...)) {
        i.remove();
    }
}

1
喜欢你在for循环中使用迭代器的方式! - manix

4

首先,我会确保这确实是性能瓶颈,否则我会选择最干净和最具表现力的解决方案。

如果确实是性能瓶颈,只需尝试不同的策略,看看哪个最快。我的建议是创建一个新的ArrayList,并将所需对象放入其中,丢弃旧ArrayList。


1
int sizepuede= listaoptionVO.size();
for (int i = 0; i < sizepuede; i++) {
    if(listaoptionVO.get(i).getDescripcionRuc()==null){
        listaoptionVO.remove(listaoptionVO.get(i));
        i--;
        sizepuede--;
     }
}

编辑:添加缩进


1

除非你确定你面临的问题确实是瓶颈,否则我会选择可读性更好的方案。

public ArrayList filterThings() {

    ArrayList pileOfThings;
    ArrayList filteredPileOfThings = new ArrayList();

    for (Thing thingy : pileOfThings) {
        if (thingy.property != 1) {
            filteredPileOfThings.add(thingy);
        }            
    }
    return filteredPileOfThings;
}

1

从ArrayList中删除元素存在隐藏成本。每次删除一个元素,您需要移动元素以填补"洞"。平均而言,对于一个包含N个元素的列表,这将需要N / 2个赋值。

因此,从N个元素的ArrayList中删除M个元素平均需要O(M * N)时间复杂度。O(N)解决方案涉及创建一个新列表。例如:

List data = ...;
List newData = new ArrayList(data.size()); 

for (Iterator i = data.iterator(); i.hasNext(); ) {
    Object element = i.next();

    if ((...)) {
        newData.add(element);
    }
}

如果N很大,我猜想这种方法会比移除(remove)的方法更快,即使M只有3或4。但是重要的是要创建足够大的“newList”来容纳所有在“list”中的元素,以避免在扩展时复制支撑数组。

0

我找到了一种更快的替代方案:

  int j = 0;
  for (Iterator i = list.listIterator(); i.hasNext(); ) {
    j++;

    if (campo.getNome().equals(key)) {
       i.remove();
       i = list.listIterator(j);
    }
  }

0
使用迭代器,您可以始终处理按顺序出现的元素,而不是指定索引。因此,您不应该为上述问题而烦恼。
Iterator itr = list.iterator();
String strElement = "";
while(itr.hasNext()){

  strElement = (String)itr.next();
  if(strElement.equals("2"))
  {
    itr.remove();
  }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接