洞察集合removeAll方法
我有一个大小〜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语句中。 非常感谢任何见解/替代解决方案。
问题是x.removeAll(y)
操作是O(n×m) ,其中n是集合x
的大小, m是集合y
的大小(即O(| x |×| y) |) )。
removeAll
方法基本上只是迭代y
每个元素的整个列表,检查x
每个元素是否恰好相等,如果是,则将其删除。 如果你能在一次通过中做到这一点会更有效率。
假设您使用的是Java 8,那么有一种更有效的方法:
List xs = new ArrayList<>(); // TODO: initialize xs with a bunch of values List ys = new ArrayList<>(); // TODO: initialize ys with a bunch of values Set ysSet = new HashSet<>(ys); List xsPrime = xs.stream() .filter(x -> !ysSet.contains(x)) .collect(Collectors.toList());
对于大小为100k的xs
和大小为66k
ys
,使用removeAll
大约需要5500ms,而使用上述方法只需要大约8ms。 由于removeAll
的二次复杂性,当你扩展到200k时,我预计差异会更加明显。
相比之下,上面使用的filter版本的复杂性将是O(n + m) ,因为它是O(m)来构建ys
中所有值的HashSet
,然后O(n)迭代所有的xs
值以确保新的ysSet
不包含任何值。 (这当然假设HashSet
查找是O(1) 。)
再次回顾你的问题,我意识到你已经在使用filter
…在这种情况下,我建议只是反转你的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
。 (无论如何,那将是我的首选解决方案。)
我刚刚再次运行我的测试,这次我添加了另一个使用循环而不是流的版本:
Set ysSet = new HashSet<>(ys); List xsPrime = new ArrayList<>(); for (Integer x : xs) { if (!ysSet.contains(x)) { xsPrime.add(x); } } return xsPrime;
这个版本在大约7毫秒而不是8毫秒完成。 由于这只比流版本稍微快一点(特别是考虑到使用removeAll
的原始版本慢了3个数量级),我会坚持使用流版本 – 特别是因为你可以利用那里的并行性(就像你已经在做的那样) with parallelStream
)。