如何从ArrayList或String数组中删除所有null元素?

Jua*_*ios 179 java performance loops for-loop arraylist

我试着用这样的循环

// ArrayList tourists

for (Tourist t : tourists) {
    if (t != null) {     
        t.setId(idForm); 
    }   
}
Run Code Online (Sandbox Code Playgroud)

但这并不好.谁能建议我一个更好的解决方案?


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

while循环,For循环和Iterator性能测试

Lit*_*ium 355

尝试:

tourists.removeAll(Collections.singleton(null));
Run Code Online (Sandbox Code Playgroud)

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

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

Mar*_*rcG 111

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

tourists.removeIf(Objects::isNull);
Run Code Online (Sandbox Code Playgroud)

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

  • 不仅因为简洁,而且因为它更具表现力.你几乎可以读到它:"来自游客,如果对象为空则删除".此外,旧方法是使用单个null对象创建新集合,然后要求从另一个中删除集合的内容.看起来有点黑客,你不觉得吗?关于速度,你有一点,如果列表真的很大并且性能是一个问题,我建议测试两种方式.我的猜测是`removeIf`更快,但这是猜测. (14认同)
  • 这应该是截至目前已接受的答案. (6认同)
  • “最好”的方式是什么?它比其他方法更快吗?或者它只是因为简洁而更具可读性? (2认同)
  • `Arrays.asList` 不是 _immutable_。它是固定大小的。 (2认同)

AZ_*_*AZ_ 46

list.removeAll(Collections.singleton(null));
Run Code Online (Sandbox Code Playgroud)

如果在Arrays.asList上使用它,它将抛出UnsupportedException,因为它为您提供了Immutable副本,因此无法修改它.见下面的代码.它创建Mutable副本,不会抛出任何异常.

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()]);
}
Run Code Online (Sandbox Code Playgroud)


Pet*_*rey 18

效率不高,但很短

while(tourists.remove(null));
Run Code Online (Sandbox Code Playgroud)

  • 实际上,@ mimrahe与快速相反.如果你有一个大清单,可怕的慢. (4认同)

小智 18

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

ImmutableList.copyOf(Iterables.filter(tourists, Predicates.notNull()))
Run Code Online (Sandbox Code Playgroud)


Mat*_*ion 7

 for (Iterator<Tourist> itr = tourists.iterator(); itr.hasNext();) {
      if (itr.next() == null) { itr.remove(); }
 }
Run Code Online (Sandbox Code Playgroud)


小智 5

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

例如:

tourists.stream().filter(Objects::nonNull).collect(Collectors.toList());
Run Code Online (Sandbox Code Playgroud)


Tat*_*ize 5

您应该使用 Java 8 之前的版本:

tourists.removeAll(Collections.singleton(null));
Run Code Online (Sandbox Code Playgroud)

后 Java 8 使用:

tourists.removeIf(Objects::isNull);
Run Code Online (Sandbox Code Playgroud)

这里的原因是时间复杂度。数组的问题是删除操作可能需要 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();
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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


for (Iterator<Tourist> itr = tourists.iterator(); itr.hasNext();) {
      if (itr.next() == null) { itr.remove(); }
 }
Run Code Online (Sandbox Code Playgroud)

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

ArrayList.this.remove(lastRet);
Run Code Online (Sandbox Code Playgroud)

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

还有:

while(tourists.remove(null));
Run Code Online (Sandbox Code Playgroud)

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


Mag*_*eri 5

我主要用的是这个:

list.removeAll(Collections.singleton(null));
Run Code Online (Sandbox Code Playgroud)

但在我学习了 Java 8 之后,我改用了这个:

List.removeIf(Objects::isNull);
Run Code Online (Sandbox Code Playgroud)