我有一个List<String> toProcess,我想进一步处理
toProcess.parallelStream().map(/*some function*/).collect(Collectors.toList());哪一种是最好的列表类型(如LinkedList、ArrayList等)从这个多线程中获得最佳速度的初始列表?
附加信息:预期的元素计数范围为10^3-10^5,但单个元素可能会变得相当大(10^5-10^6字符)。
另外,我可以在整个地方使用String[],因为字符串的数量保证不会改变(结果将包含与toProcess一样多的元素)。
无论哪种方式,我都必须在结束时按顺序遍历所有元素。目前,我使用foreach-loop来组装最终结果。这可以很容易地更改为常规的for-loop。
发布于 2015-04-18 05:19:54
如果您确信输出元素的数量等于输入元素的数量,并且对结果的数组感到满意,那么肯定使用toArray而不是收集器。如果管道始终具有固定的大小,则将以正确的大小预先分配目标数组,并且并行操作将其结果直接存放在正确位置的目标数组中:不复制、重新分配或合并。
如果您想要一个List,可以始终使用Arrays.asList包装结果,但当然不能向结果添加或删除元素。
收集器
如果上述条件之一不成立,那么您需要处理收集器,这有不同的权衡。
收集器以线程受限的方式对中间结果进行并行工作.然后将中间结果合并到最终结果中。有两个操作需要考虑: 1)将单个元素累加到中间结果;2)将中间结果合并(或合并)为最终结果。
在LinkedList和ArrayList之间,ArrayList可能更快,但您可能应该对此进行基准测试以确定。注意,Collectors.toList默认使用ArrayList,尽管这在以后的版本中可能会改变。
LinkedList
每个正在累积的元素(LinkedList.add)都涉及到分配一个新的列表节点并将其连接到列表的末尾。将节点连接到列表非常快,但这涉及到对每个流元素的分配,这可能会在积累过程中导致少量的垃圾收集。
合并(LinkedList.addAll)也相当昂贵。第一步是将源列表转换为数组;这是通过遍历列表的每个节点并将元素存储到一个临时数组来完成的。然后,代码遍历这个临时数组,并将每个元素添加到目标列表的末尾。这需要为每个元素分配一个新节点,如上所述。因此,合并操作非常昂贵,因为它迭代源列表中的每个元素两次,并对每个元素进行分配,这可能会带来垃圾收集开销。
ArrayList
每个元素的积累通常涉及将其附加到ArrayList中包含的数组的末尾。这通常相当快,但如果数组已满,则必须重新分配并复制到更大的数组中。ArrayList的增长策略是将新数组分配给比当前数组大50%的数组,因此重新分配与添加元素数量的日志成正比,这并不是太糟糕。但是,必须复制所有元素,这意味着可能需要多次复制早期的元素。
合并一个ArrayList可能比LinkedList便宜得多。将ArrayList转换为数组涉及从源元素到临时数组的大容量复制(而不是一次)。如果需要,将调整目标数组的大小(在本例中可能如此),需要对所有元素进行批量复制。然后将源元素从临时数组大容量复制到目标,目标已预先调整大小以容纳它们。
讨论
鉴于以上所述,似乎ArrayList将比LinkedList更快。但是,即使集合到ArrayList,也需要对许多元素进行不必要的重新分配和复制,可能是多次的。未来的优化可能是Collectors.toList将元素累加到为快速附加访问而优化的数据结构中,最好是预先调整大小以适应预期的元素数。支持快速合并的数据结构也是可能的。
如果您所需要做的只是迭代最终结果,那么使用具有这些属性的您自己的数据结构应该不会太困难。如果不需要一个完整的列表,那么就有可能进行重大的简化。它可以累积到预先大小的列表中,以避免重新分配,而合并只会将这些聚集到树结构或列表列表中。有关想法,请参阅JDK的SpinedBuffer (一个私有实现类)。
发布于 2015-04-18 04:36:55
通常,与ArrayList相比,ArrayList对并行化要友好得多,因为数组很容易分割成碎片,然后交给每个线程。
但是,由于您的终端操作是将结果写入文件,所以并行化可能根本无助于您,因为您可能会受到IO而不是CPU的限制。
https://stackoverflow.com/questions/29711208
复制相似问题