Java 8流列表收集器内存分配速度与带预分配的循环
我想知道如果终端操作是列表收集器,Java8流如何处理内存分配 举个例子Java 8流列表收集器内存分配速度与带预分配的循环,java,memory,java-8,java-stream,Java,Memory,Java 8,Java Stream,我想知道如果终端操作是列表收集器,Java8流如何处理内存分配 举个例子 List<Integer> result = myList.stream().map(doWhatever).collect(Collectors.toList()); List result=myList.stream().map(doWhatever.collect(Collectors.toList()); vs List result=newarraylist(myList.size()); for
List<Integer> result = myList.stream().map(doWhatever).collect(Collectors.toList());
List result=myList.stream().map(doWhatever.collect(Collectors.toList());
vs
List result=newarraylist(myList.size());
for(字符串s:myList){
结果。添加(doWhatever.apply(s));
}
在使用流的情况下,不知道列表将增长多大,这意味着必须进行某种重新分配。这个假设是真的吗
结果列表的类型是否是某种链表,因此对元素的访问速度比ArrayList慢
如果我从一开始就知道结果列表的大小,我是否应该将流与列表收集器一起使用?如果您查看
收集器的源代码。toList()
,则它不会预分配
public static <T> Collector<T, ?, List<T>> toList() {
return new CollectorImpl<>((Supplier<List<T>>) ArrayList::new, List::add,
(left, right) -> { left.addAll(right); return left; },
CH_ID);
}
公共静态收集器toList(){
返回新的CollectorImpl((供应商)ArrayList::new,List::add,
(左,右)->{left.addAll(右);返回left;},
(中华人民共和国),;
}
它只创建一个新的ArrayList
,默认大小,然后在随后调用add
/addAll
时,它会在幕后调整大小,从而允许将流的结果元素收集到使用默认构造函数创建的数组列表中,因此默认容量为10
,因此在这种情况下确实需要重新分配大小超过10
如果要使用不同的列表
实现,请使用更通用的收集器,该收集器允许提供目标集合的工厂
例如,如果要将元素收集到LinkedList
中,则可以将代码重写为下一步:
List<Integer> result = myList.stream()
.map(doWhatever)
.collect(Collectors.toCollection(LinkedList::new));
List result=myList.stream()
.map(doWhatever)
.collect(Collectors.toCollection(LinkedList::new));
假设您想要一个默认容量为100
的ArrayList
,则收集器将是收集器。toCollection(()->new ArrayList(100))
收集器。toList()
不会指定任何有关其实现的内容。如果需要,请使用收集(ArrayList::new)
如果我从一开始就知道结果列表的大小,我是否应该将流与列表收集器一起使用
不,去用吧。相对于简洁性而言,分配成本低廉,成本最低。预先确定列表大小通常是一种过早的优化
在使用流的情况下,不知道列表将增长多大,这意味着必须进行某种重新分配。这个假设是真的吗
它知道前面的管道及其大小,并创建一个ArrayList
,默认配置不查看它。当您使用动态优化良好的阵列时,这并不重要
结果列表的类型是否是某种链表,因此对元素的访问速度比ArrayList慢
默认情况下使用ArrayList
,但您可以自由提供自己的供应商和累加器来更改此行为:
stream.collect(() -> new ArrayList<>(SIZE), ArrayList::add, ArrayList::addAll);
stream.collect(()->新建ArrayList(大小),ArrayList::add,ArrayList::addAll);
如果我从一开始就知道结果列表的大小,我是否应该将流与列表收集器一起使用
别想那个。除了简洁的语法外,Stream API还提供了许多功能强大的东西(如并行化)供您使用。目前,toList()
收集器是通过使用并返回ArrayList
来实现的(请注意,在收集过程中使用的容器并不总是必须匹配最终结果的类型)。按照定义收集器接口的方式,收集器没有机会预先调整列表的大小
但原则上,由于标准流实现和预定义的toList()
收集器实现是同一个库的一部分,因此在流检测到toList()的未来实现(或替代JRE)中可能存在非标准通信
收集器,并执行优化操作。但是当使用toList()
收集器时,例如作为groupingBy
收集器的下游收集器,无论如何都没有可预测的大小
如果您假设流可以预测其大小,如您的myList.stream().map(doWhatever)
示例中所示,考虑到当前的实现,最有效的解决方案是
List<ElementType> result=Arrays.asList(stream.toArray(ElementType[]::new));
List result=Arrays.asList(stream.toArray(ElementType[]::new));
因为该操作将利用已知的大小,即使是并行的,或者特别是当与并行流一起使用时,当拆分的子大小是可预测的,因为此时不需要合并步骤,即所有工人将直接写入结果数组
不幸的是,如果ElementType
不是可重新定义的类型,则必须在此处使用未经检查的操作
如果大小不可预测,则与当前的toList()
收集器相比,此解决方案的效率可能更高,但与可以使用非线性存储的未来实现相比,此解决方案可能会有所松动
因此,优化的变体仅与特定设置相关。在大多数情况下,toList()
收集器就足够了,甚至可能比未来可能的实现中的任何替代方案都要好。对于大型并行流,我发现toList()实际上存在严重的性能问题,因为累加器列表被反复组合,这导致了类似于(N^2)大于O(N)
这里有一个替代的toList()收集器
List<ElementType> result=Arrays.asList(stream.toArray(ElementType[]::new));