如何有效地从 ArrayList 或字符串数​​组中删除所有空元素?

Posted

技术标签:

【中文标题】如何有效地从 ArrayList 或字符串数​​组中删除所有空元素?【英文标题】:How to efficiently remove all null elements from a ArrayList or String Array? 【发布时间】:2011-06-16 17:15:46 【问题描述】:

我尝试这样的循环

// ArrayList tourists

for (Tourist t : tourists) 
    if (t != null)      
        t.setId(idForm); 
       

但这并不好。谁能给我一个更好的解决方案?


一些有用的基准来做出更好的决定:

While loop, For loop and Iterator Performance Test

【问题讨论】:

使用Iterator?挖掘 java-doc。 download.oracle.com/javase/6/docs/api/java/util/… 由于您的基准测试参考,您似乎将“nice”/“better”定义为“效率”基准。您的参考资料本身似乎以答案结尾:“迭代器循环是最慢的,for 循环和 while 循环之间的区别并不那么显着。” 【参考方案1】:
 for (Iterator<Tourist> itr = tourists.iterator(); itr.hasNext();) 
      if (itr.next() == null)  itr.remove(); 
 

【讨论】:

这在您必须在遍历时删除元素时会更有用。巧合的是,我将元素归零而不是尝试使用removeAll(..null..)。谢谢! 您最好将值设置为 null 然后在最后删除。 removeAll 中的 batchRemove 横穿列表,具有读取和写入位置并迭代列表一次,当它遇到空值时移动读取但不移动写入。 .remove() 每次调用它时可能需要对整个数组进行数组复制。【参考方案2】:

试试:

tourists.removeAll(Collections.singleton(null));

阅读Java API。对于不可变列表(例如使用Arrays.asList 创建的),代码将抛出java.lang.UnsupportedOperationException;详情请见this answer。

【讨论】:

List.removeAll() 的时间复杂度为 n^2。只是说。 对于 Java 8 或更高版本,请参阅下面的@MarcG 答案。 @Hemanth 您能否详细说明您是如何获得时间复杂度的?因为在我看来,ArrayListLinkedList 都相当 O(n) @1blustone N^2 在这里没有意义,因为两个集合的大小不相关;最终会是N*M,但情况并非总是如此。 ArrayList 覆盖您链接的方法定义以减轻执行多次删除的成本,这将其变成 N*T(c.contains);因此,如果参数c 中的集合是HashSet,则它将是O(N);如果是TreeSet,它将是O(N*log M)。同样的复杂性适用于LinkedList,他们不必付出太多努力,因为根据定义,删除很便宜(如果你有对节点的引用)。 @Hemanth 不,不是。它是 n*m,在这种情况下,m 是元素的数量,它是 1 的 null 单例。它是 O(n)。您可以在此处查看源代码,并看到它确实对列表进行了一次读取和写入,移动元素以考虑删除的元素。【参考方案3】:

效率不高,但时间短

while(tourists.remove(null));

【讨论】:

不幸的是,您的解决方案是唯一对我有用的解决方案...谢谢! 简单快速 @mimrahe 实际上与快速相反。如果您的列表很大,速度会非常慢。【参考方案4】:

我玩弄了这个,发现 trimToSize() 似乎有效。我在 android 平台上工作,所以可能会有所不同。

【讨论】:

根据 javadoc,trimToSize 不会修改 ArrayList 的内容。如果这在 android 中有所不同,则可能是一个错误。【参考方案5】:

有一种简单的方法可以从collection 中删除所有null 值。您必须将包含null 作为参数的集合传递给removeAll() 方法

List s1=new ArrayList();
s1.add(null);

yourCollection.removeAll(s1);

【讨论】:

这对我来说效果最好。它还允许您在“过滤器数组”中轻松添加多个条目,这些条目将传递到原始集合的 removeAll 方法中。【参考方案6】:

如果你更喜欢不可变的数据对象,或者你只是不想破坏输入列表,你可以使用 Guava 的谓词。

ImmutableList.copyOf(Iterables.filter(tourists, Predicates.notNull()))

【讨论】:

【参考方案7】:
list.removeAll(Collections.singleton(null));

如果您在 Arrays.asList 上使用它会抛出 UnsupportedException,因为它会为您提供 Immutable 副本,因此无法对其进行修改。请参阅下面的代码。它创建可变副本并且不会抛出任何异常。

public static String[] clean(final String[] v) 
    List<String> list = new ArrayList<String>(Arrays.asList(v));
    list.removeAll(Collections.singleton(null));
    return list.toArray(new String[list.size()]);

【讨论】:

【参考方案8】:

这是从数组列表中删除默认空值的简单方法

     tourists.removeAll(Arrays.asList(null));  

否则字符串值“null”从数组列表中删除

       tourists.removeAll(Arrays.asList("null"));  

【讨论】:

【参考方案9】:

我们可以使用相同的迭代器来删除所有的空值。

Iterator<Tourist> itr= tourists.iterator();
while(itr.hasNext())
    if(itr.next() == null)
        itr.remove();
    

【讨论】:

【参考方案10】:

截至 2015 年,这是最好的方法(Java 8):

tourists.removeIf(Objects::isNull);

注意:此代码将抛出java.lang.UnsupportedOperationException 用于固定大小的列表(例如使用 Arrays.asList 创建的),包括不可变列表。

【讨论】:

“最佳”以什么方式?它比其他方法更快吗?还是因为简洁而更具可读性? 不仅因为简洁,而且因为它更具表现力。您几乎可以阅读它:“来自游客,如果对象为空,则删除”。此外,旧方法是使用单个 null 对象创建一个新集合,然后要求从另一个集合中删除一个集合的内容。似乎有点骇人听闻,你不觉得吗?关于速度,你有一点,如果列表真的很大并且性能是一个问题,我会建议测试两种方式。我的猜测是removeIf 更快,但这是一个猜测。 Arrays.asList 不是不可变的。它的大小是固定的。 @turbanoff 是的,你是对的,当然。它只是固定大小的,我会更新答案。【参考方案11】:

我将流接口与流操作 collect 和辅助方法一起使用来生成一个新列表。

tourists.stream().filter(this::isNotNull).collect(Collectors.toList());

private <T> boolean isNotNull(final T item) 
    return  item != null;

【讨论】:

tourists.stream().filter(s -&gt; s != null).collect(Collectors.toList()); tourists.stream().filter(Objects::nonNull).collect(Collectors.toList())【参考方案12】:

Objects 类有一个nonNull Predicate 可以与filter 一起使用。

例如:

tourists.stream().filter(Objects::nonNull).collect(Collectors.toList());

【讨论】:

欢迎来到 Stack Overflow。在回答问题时,请尝试添加对代码的解释。请返回并编辑您的答案以包含更多信息。【参考方案13】:

使用 Java 8,您可以使用 stream()filter() 来做到这一点

tourists = tourists.stream().filter(t -> t != null).collect(Collectors.toList())

tourists = tourists.stream().filter(Objects::nonNull).collect(Collectors.toList())

欲了解更多信息:Java 8 - Streams

【讨论】:

此解决方案适用于不可变副本,即 --> List listOfString = Arrays.asList("test1",null,"test"); ..... 也 !谢谢【参考方案14】:

你应该使用 Pre-Java 8:

tourists.removeAll(Collections.singleton(null));

Java 8 后使用:

tourists.removeIf(Objects::isNull);

这里的原因是时间复杂度。数组的问题是删除操作可能需要 O(n) 时间才能完成。实际上,在 Java 中,这是一个剩余元素的数组副本,被移动以替换空白点。此处提供的许多其他解决方案将触发此问题。前者在技术上是 O(n*m),其中 m 为 1,因为它是一个单例 null:所以 O(n)

您应该删除所有单例,在内部它执行一个具有读取位置和写入位置的 batchRemove()。并迭代列表。当它达到空值时,它只是将读取位置迭代 1。当它们相同时它通过,当它们不同时它继续复制值。然后在最后修剪到大小。

它在内部有效地做到了这一点:

public static <E> void removeNulls(ArrayList<E> list) 
    int size = list.size();
    int read = 0;
    int write = 0;
    for (; read < size; read++) 
        E element = list.get(read);
        if (element == null) continue;
        if (read != write) list.set(write, element);
        write++;
    
    if (write != size) 
        list.subList(write, size).clear();
    

您可以明确看到的是 O(n) 操作。

唯一可能更快的是,如果您从两端迭代列表,并且当您找到一个空值时,您将其值设置为您在最后找到的值,并减少该值。并迭代直到两个值匹配。你会弄乱顺序,但会大大减少值的数量 你设置与那些你一个人留下的。这是一个很好的了解方法,但在这里无济于事,因为 .set() 基本上是免费的,但这种形式的删除对你的腰带来说是一个有用的工具。


for (Iterator<Tourist> itr = tourists.iterator(); itr.hasNext();) 
      if (itr.next() == null)  itr.remove(); 
 

虽然这看起来很合理,但迭代器上的 .remove() 在内部调用:

ArrayList.this.remove(lastRet);

这又是删除中的 O(n) 操作。如果您关心速度,它会执行一个 System.arraycopy() ,这又不是您想要的。这使它成为 n^2。

还有:

while(tourists.remove(null));

这是 O(m*n^2)。在这里,我们不仅迭代列表。每次匹配空值时,我们都会重复整个列表。然后我们执行 n/2(平均)操作来执行 System.arraycopy() 来执行删除。 从字面上看,您可以在具有值的项目和具有空值的项目之间对整个集合进行排序,并在更短的时间内修剪结尾。事实上,这对所有破碎的人来说都是如此。至少在理论上,实际的 system.arraycopy 实际上并不是 N 操作。理论上,理论和实践是一回事;实际上它们不是。

【讨论】:

【参考方案15】:

使用 Java 8,这可以使用流、并行流和removeIf 方法以各种方式执行:

List<String> stringList = new ArrayList<>(Arrays.asList(null, "A", "B", null, "C", null));
List<String> listWithoutNulls1 = stringList.stream()
                .filter(Objects::nonNull)
                .collect(Collectors.toList()); //[A,B,C]
List<String> listWithoutNulls2 = stringList.parallelStream()
                .filter(Objects::nonNull)
                .collect(Collectors.toList()); //[A,B,C]
stringList.removeIf(Objects::isNull); //[A,B,C]

并行流将利用可用的处理器,并将加快处理合理大小的列表。始终建议在使用流之前进行基准测试。

【讨论】:

【参考方案16】:

类似于@Lithium 的答案,但不会抛出“列表可能不包含空类型”错误:

   list.removeAll(Collections.<T>singleton(null));

【讨论】:

【参考方案17】:
List<String> colors = new ArrayList<>(
Arrays.asList("RED", null, "BLUE", null, "GREEN"));
// using removeIf() + Objects.isNull()
colors.removeIf(Objects::isNull);

【讨论】:

【参考方案18】:

主要是我用这个:

list.removeAll(Collections.singleton(null));

但是在我学习了 Java 8 之后,我切换到了这个:

List.removeIf(Objects::isNull);

【讨论】:

以上是关于如何有效地从 ArrayList 或字符串数​​组中删除所有空元素?的主要内容,如果未能解决你的问题,请参考以下文章

如何更有效地从n组中找到满足给定条件的最小组合?

如何有效地从大型 Excel 文档中检索所有字符串

如何有效地从大 txt 文件中读取字符串

如何有效地从另一个字符串中找到的字符串中删除重复字符?

如何有效地从 jupyter 或 colab 中的数据帧复制输出并以漂亮/可读的格式粘贴到 ***

如何在 pySpark 中有效地从字符串数据框中替换多个正则表达式模式的所有实例?