用于过滤Java Stream的最有效集合?

时间:2016-03-30 13:42:38

标签: java collections java-stream

我在集合中存储了多个Thing个。个人Thing是唯一的,但他们的类型不是。它们的存储顺序也无关紧要。

我想使用Java 8的Stream API使用此代码搜索特定类型:

Collection<Thing> things = ...;
// ... populate things ...
Stream<Thing> filtered = things.stream.filter(thing -> thing.type.equals(searchType));

是否有特定的Collection会使filter()更有效率?

我倾向于不这么认为,因为过滤器必须遍历整个集合。

另一方面,如果集合是某种由Thing.type索引的树,那么filter()可能会利用这一事实。有没有办法实现这个目标?

3 个答案:

答案 0 :(得分:2)

像过滤器这样的流操作不是专门用于在特殊情况下获益的。例如,IntStream.range(0, 1_000_000_000).filter(x -> x > 999_999_000)实际上会迭代所有输入数字,它不能只是“跳过”第一个999_999_000。所以你的问题减少了,找到了最有效迭代的集合。

迭代通常在Spliterator.forEachRemaining方法(用于非短路流)和Spliterator.tryAdvance方法(用于短路流)中执行,因此您可以查看相应的分裂器实施并检查它的效率。我认为最有效的是一个数组(裸露或包含在Arrays.asList列表中):它具有最小的开销。 ArrayList也非常快,但是对于短路操作,它会在每次迭代时检查modCount(以检测并发修改),这会增加非常小的开销。其他类型如HashSetLinkedList的速度相对较慢,但在大多数应用中,这种差异几乎无关紧要。

请注意,应谨慎使用并行流。例如,LinkedList的拆分非常差,您可能会遇到比连续情况更差的性能。

答案 1 :(得分:2)

关于这个问题,最重要的是要理解当你将lambda表达式传递给像Stream API这样的特定库时,所有库接收的都是函数接口的实现,例如: Predicate的一个实例。它不知道该实现将做什么,因此无法利用通过比较过滤排序数据等方案。流库根本不知道Predicate正在进行比较。

执行此类优化的实现需要知道并理解代码的JVM与知道语义的库的交互。这种情况在目前的实施中并没有发生,目前很遥远,至少我可以看到它。

如果源是树或排序列表,并且您希望从中获益以进行过滤,则必须在创建流之前使用在源上运行的API来执行此操作。例如。假设我们有一个TreeSet,并希望过滤它以获取特定范围内的项目,例如

// our made-up source
TreeSet<Integer> tree=IntStream.range(0, 100).boxed()
    .collect(Collectors.toCollection(TreeSet::new));
// the naive implementation
tree.stream().filter(i -> i>=65 && i<91).forEach(i->System.out.print((char)i.intValue()));

我们可以改为:

tree.tailSet(65).headSet(91).stream().forEach(i->System.out.print((char)i.intValue()));

将利用排序/树性质。当我们有一个排序列表时,请说

List<Integer> list=new ArrayList<>(tree);

利用排序的性质更复杂,因为集合本身并不知道它已经排序并且不提供直接利用它的操作:

int ix=Collections.binarySearch(list, 65);
if(ix<0) ix=~ix;
if(ix>0) list=list.subList(ix, list.size());
ix=Collections.binarySearch(list, 91);
if(ix<0) ix=~ix;
if(ix<list.size()) list=list.subList(0, ix);
list.stream().forEach(i->System.out.print((char)i.intValue()));

当然,这里的流操作只是示例性的,你根本不需要流,当你所做的只是forEach ...

答案 2 :(得分:1)

据我所知,普通流媒体没有这种差异。

但是,当您使用易于分离的集合(如LinkedList over LinkedList或任何类型的Set)时,使用并行流时可能会更好。