我有一个大小约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语句中。非常感谢任何见解/替代解决方案。
答案 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
)。