Java 8流列表收集器内存分配速度与带预分配的循环

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

我想知道如果终端操作是列表收集器,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(字符串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));