我在集合中存储了多个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()
可能会利用这一事实。有没有办法实现这个目标?
答案 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
(以检测并发修改),这会增加非常小的开销。其他类型如HashSet
或LinkedList
的速度相对较慢,但在大多数应用中,这种差异几乎无关紧要。
请注意,应谨慎使用并行流。例如,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)时,使用并行流时可能会更好。