在官方文档中,您可以阅读:
UNORDERED
表示集合操作未提交 保留输入元素的遭遇顺序。
没有任何例子,这没有太大帮助。
我的问题是,UNORDERED
特征究竟是什么意思?我应该将它用于减少收藏家,如分钟或总和,还是只适用于收藏家?
在OpenJDK中看起来像减少操作(min,sum,avg)具有空特征。我希望在那里找到至少CONCURRENT
和UNORDERED
。
答案 0 :(得分:12)
在没有特殊请求的情况下,流操作必须表现得就像在源的遭遇顺序中处理元素一样。对于某些操作 - 例如使用关联操作进行简化 - 可以遵守此约束并仍然可以获得有效的并行执行。但是对于其他人来说,这种约束是非常有限的。并且,对于某些问题,此约束对用户没有意义。请考虑以下流管道:
people.stream()
.collect(groupingBy(Person::getLastName,
mapping(Person::getFirstName));
与“Smith”关联的名字列表是否按照它们出现在初始流中的顺序出现在地图中是否重要?对于某些问题,是的,对于某些问题 - 我们不希望流库为我们猜测。一个有序的收集器说,可以将一个名字插入到列表中,其顺序与Smith姓氏人在输入源中出现的顺序不一致。通过放宽此约束,有时(并非总是),流库可以提供更高效的执行。
例如,如果您不关心此订单保留,则可以将其执行为:
people.parallelStream()
.collect(groupingByConcurrent(Person::getLastName,
mapping(Person::getFirstName));
并发收集器是无序的,这允许优化共享基础ConcurrentMap
,而不是具有O(log n)
映射合并步骤。放宽排序约束可以实现真正的算法优势 - 但我们不能假设约束无关紧要,我们需要用户告诉我们这一点。使用UNORDERED
收集器是告诉流库这些优化是公平游戏的一种方法。
答案 1 :(得分:6)
UNORDERED
实质上意味着收集器既是关联的(规范要求)又可交换的(不是必需的)。
关联性允许将计算分成子部分,然后将它们组合成完整的结果,但需要严格排序组合步骤。从docs:
中检查此代码段 A a2 = supplier.get();
accumulator.accept(a2, t1);
A a3 = supplier.get();
accumulator.accept(a3, t2);
R r2 = finisher.apply(combiner.apply(a2, a3)); // result with splitting
在最后一步combiner.apply(a2, a3)
中,参数必须以此顺序出现,这意味着整个计算管道必须跟踪顺序并最终尊重它。
另一种说法是,必须对从递归拆分中获得的树进行排序。
另一方面,如果组合操作是可交换的,我们可以将任何子部分与任何其他子部分组合,无需特定顺序,并始终获得相同的结果。显然,这会在空间和时间方面带来许多优化机会。
应该注意的是,JDK中有UNORDERED
个收集器,它们不能保证交换性。主要类别是与其他下游收集器组成的“高阶”收集器,但它们不强制执行UNORDERED
属性。
答案 2 :(得分:4)
内部Collector.Characteristics
类本身在描述中相当简洁,但是如果你花几秒钟探索上下文,你会发现包含Collector接口提供了额外的信息
对于没有UNORDERED特征的收藏家,如果finisher.apply(a1).equals(finisher.apply(a2)),则两个累积结果a1和a2相等。对于无序收集器,放宽等价以允许与顺序差异相关的不相等。 (例如,如果元素包含相同的元素,忽略顺序,那么将元素累积到List的无序收集器会考虑两个等价的列表。)
在OpenJDK看起来像减少操作(min,sum,avg)有空特征,我期望在那里找到至少CONCURRENT和UNORDERED。
至少对于双精度求和,平均值肯定是有序的而不是并发的,因为求和逻辑使用子结果合并,而不是线程安全的累加器。