深入了解集合removeAll方法

时间:2015-10-20 03:41:06

标签: java collections

我有一个大小约200k的列表。我在过滤列表时遇到了一些问题。

以下是实施:

public List<> filterList(List<> listToBeFiltered){
List<> removeElementsFromList = listToBeFiltered.parallelStream()
                                    .filter(//some filtering logic)
                                    .collect(Collectors.toList());
listToBeFiltered.removeAll(removeElementsFromList);
return listToBeFiltered;
}

我面对代码的问题是,当removeElementsFromList接近listToBeFiltered的大小时,程序将一直停留在removeAll语句中。非常感谢任何见解/替代解决方案。

1 个答案:

答案 0 :(得分:2)

问题是x.removeAll(y)操作是 O(n×m),其中 n 是集合x的大小,和 m 是集合y的大小(即 O(| x |×| y |))。

removeAll方法基本上只是迭代y中每个元素的整个列表,检查x中的每个元素是否恰好相同,如果是,则将其删除。如果你能在一次通过中做到这一点会更有效率。

假设您正在使用Java 8,那么有一种更有效的方法:

List<Integer> xs = new ArrayList<>();
// TODO: initialize xs with a bunch of values
List<Integer> ys = new ArrayList<>();
// TODO: initialize ys with a bunch of values
Set<Integer> ysSet = new HashSet<>(ys);
List<Integer> xsPrime = xs.stream()
    .filter(x -> !ysSet.contains(x))
    .collect(Collectors.toList());

对于大小为{100}且大小为xs ys的{​​{1}},使用66k大约需要5500毫秒,而使用上述方法只需要大约8毫秒。由于removeAll的二次复杂性,当你扩展到200k时我会发现差异更加明显。

相比之下,上面使用的过滤器版本的复杂性将是 O(n + m),因为它是 O(m)来构建{{ 1}} removeAll中的所有值,然后 O(n)迭代HashSet的所有值,以确保新ys中不包含任何值xs 1}}。 (这当然假设ysSet查找 O(1)。)

再次回顾你的问题,我意识到你已经在使用HashSet ...在这种情况下,我建议只是反转你的过滤器逻辑,然后将传入列表的值重置为过滤值:

filter

如果您不需要改变原始列表,那么您可以直接返回public List<> filterList(List<> listToBeFiltered){ List<> filteredList = listToBeFiltered.parallelStream() .filter(/* some inverted filtering logic */) .collect(Collectors.toList()); listToBeFiltered.clear(); listToBeFiltered.addAll(filteredList); return listToBeFiltered; } 。 (无论如何,那将是我的首选解决方案。)

我刚刚再次运行测试,这次我添加了另一个使用循环而不是流的版本:

filteredList

这个版本在大约7毫秒而不是8毫秒完成。由于这只比流版本略快(特别是考虑到使用Set<Integer> ysSet = new HashSet<>(ys); List<Integer> xsPrime = new ArrayList<>(); for (Integer x : xs) { if (!ysSet.contains(x)) { xsPrime.add(x); } } return xsPrime; 的原始版本慢了3个数量级),我会坚持使用流版本 - 特别是因为你可以利用那里的并行性(就像你一样) '已经在使用removeAll)。