如何从ArrayList或String Array中删除所有空元素?


187

我尝试这样的循环

// ArrayList tourists

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

但这不是很好。谁能建议我一个更好的解决方案?


一些有用的基准可以做出更好的决策:

While循环,For循环和迭代器性能测试


Answers:


365

尝试:

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

阅读Java API。该代码将抛出java.lang.UnsupportedOperationException不可变的列表(例如使用创建的Arrays.asList);有关更多详细信息,请参见此答案


9
的时间复杂度List.removeAll()n ^ 2。只是说。
Hemanth '16

8
对于Java 8或更高版本,请参见下面的@MarcG答案。
安迪·托马斯

2
@Hemanth您能详细说明一下如何计算时间复杂度吗?因为它看起来相当O(n)给我两个ArrayListLinkedList
Helder Pereira

1
@HelderPereira我不认为这种情况适用,因为(行349)似乎遍历两个列表(遍历contains()整个数组),并且因为singleton它只是一个元素N * 1 = N。但是一般来说N^2
Moira

6
@Hemanth不,不是。它是n * m,其中m是元素的数目,在这种情况下,n为1的null单身。它是O(n)。您可以在此处看到源代码,并看到它确实在列表中进行了一次读写,将元素移到了被删除的元素上。
tar

116

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

tourists.removeIf(Objects::isNull);

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


1
“最佳”采用什么方式?它比其他方法快吗?还是因为简洁而更具可读性?
安迪·托马斯

15
不仅因为简洁,而且因为它更具表现力。您几乎可以看得出来:“从游客那里,如果object为null,则删除”。同样,旧方法是用一个空对象创建一个新的集合,然后要求从另一个集合中删除一个集合的内容。似乎有点骇人听闻,您不觉得吗?关于速度,您有一点要说,如果列表确实很大并且性能是一个问题,我建议同时进行两种测试。我的猜测removeIf是更快,但这只是一个猜测。
MarcG '16

1
Arrays.asList不是一成不变的。它是固定大小的。
turbanoff

@turbanoff是的,您当然是对的。它只有固定大小,我将更新答案。
MarcG '16

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

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

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()]);
}

18

没有效率,但是很短

while(tourists.remove(null));

1
不幸的是,您的解决方案是唯一对我有用的解决方案...谢谢!
Pkmmte 2014年

简单又快速

5
@mimrahe实际上与fast相反。如果列表很大,那就太慢了。
Gewure

18

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

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

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

当您必须在遍历时删除元素时,这可能会更加有用。巧合的是,我比尝试使用要使元素无效removeAll(..null..)。谢谢!
Mustafa 2013年

您最好将这些值设置为null,然后在最后将其删除。removeAll中的batchRemove遍历列表,具有读写位置,并且对列表进行一次迭代,当读取的内容为null时,移动读取的内容而不移动写入的内容。.remove()可能每次调用时都要合法复制整个数组。
Ta

4

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);

这也是remove中的O(n)操作。如果您关心速度,它将执行System.arraycopy(),这又不是您想要的。这使其为n ^ 2。

还有:

while(tourists.remove(null));

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


3

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

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

yourCollection.removeAll(s1);

这对我来说效果最好。它还使您可以轻松地在“过滤器数组”中添加多个条目,这些条目将传递到原始集合的removeAll方法中。

3

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

例如:

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

1
欢迎使用堆栈溢出。回答问题时,请尝试添加代码说明。请返回并编辑您的答案以包含更多信息。
泰勒

3

使用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-流


1
该解决方案适用于不可变副本,即-> List <String> listOfString = Arrays.asList(“ test1”,null,“ test”); .....也是!谢谢
Anurag_BEHS

2

这是从arraylist中删除默认null值的简单方法

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

否则,将字符串值“ null”从arraylist中删除

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

1

我玩了一下,发现trimToSize()似乎可以工作。我正在使用Android平台,因此可能有所不同。


2
根据javadoc,trimToSize不会修改的内容ArrayList。如果这在android中有所不同,则可能是一个错误。
fabian

1

我们可以对它使用iterator来删除所有空值。

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

1

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

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

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

2
tourists.stream().filter(s -> s != null).collect(Collectors.toList());
1ac0

1

我主要是用这个:

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

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

List.removeIf(Objects::isNull);

0

使用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]

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


0

类似于@Lithium答案,但不会引发“列表可能不包含null类型”错误:

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

0
List<String> colors = new ArrayList<>(
Arrays.asList("RED", null, "BLUE", null, "GREEN"));
// using removeIf() + Objects.isNull()
colors.removeIf(Objects::isNull);
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.