如何有效地从 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 您能否详细说明您是如何获得时间复杂度的?因为在我看来,ArrayList
和 LinkedList
都相当 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 -> 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你应该使用 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 或字符串数组中删除所有空元素?的主要内容,如果未能解决你的问题,请参考以下文章