[英]Java stream - collect combiner
为什么以下代码:
StringBuilder sb22 = IntStream
.range(1, 101)
.filter(x -> x > 50)
.boxed()
.parallel()
.collect(// object that is used in accumulator to do accumulating on
StringBuilder::new,
// use object from above and call append on it with each stream element as argument
(sb, a) -> sb.append(":" + a),
// (executes only when using parallel!)
(sb1, sb2) -> {
System.out.println(Thread.currentThread().getId() + " " + "sb1=" + sb1 + " AND " + "sb2=" + sb2);
sb1.append("-"+sb2);
});
产生这个结果:
------------------:51:52:53-:54:55:56-:57:58:59-:60:61:62-:63:64:65-:66:67:68-:69:70:71-:72:73-:74:75-:76:77:78-:79:80:81-:82:83:84-:85:86:87-:88:89:90-:91:92:93-:94:95:96-:97:98-:99:100
不应该首先将部分( ------------------
)排除在输出之外?
此外,我理解收集中的组合器可能会被无序调用,因此可以改为:76:77:78-:79:80:81
例如:63:64:65-:79:80:81
?
更新 (@Holger回复后)
这是使用他为此案例链接的代码生成的树:
[51..100]
_________________________________________________________________________________/\______________________________________________________________________
| |
(empty) [51..100]
___________________________________/\__________________________________ ________________________________________/\______________________________________
| | | |
(empty) (empty) [51..75] [76..100]
___________________/\______________ ___________________/\______________ ______________________/\________________ ______________________/\________________
| | | | | | | |
(empty) (empty) (empty) (empty) [51..62] [63..75] [76..87] [88..100]
_______/\______ ___________/\______ _______/\______ ___________/\______ ________/\_______ _____________/\_______ ________/\_______ _____________/\_______
| | | | | | | | | | | | | | | |
(empty) (empty) (empty) (empty) (empty) (empty) (empty) (empty) [51..56] [57..62] [63..68] [69..75] [76..81] [82..87] [88..93] [94..100]
___/\__ ___/\__ ___/\__ _______/\__ ___/\__ ___/\__ ___/\__ _______/\__ ___/\___ ___/\___ ___/\___ ________/\__ ___/\___ ___/\___ ___/\___ ________/\___
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
(empty) (empty) (empty) (empty) (empty) (empty) (empty) (empty) (empty) (empty) (empty) (empty) (empty) (empty) (empty) (empty) [51..53] [54..56] [57..59] [60..62] [63..65] [66..68] [69..71] [72..75] [76..78] [79..81] [82..84] [85..87] [88..90] [91..93] [94..96] [97..100]
___/\__ ___/\__ ___/\___ ____/\__
| | | | | | | |
(empty) (empty) (empty) (empty) [72..73] [74..75] [97..98] [99..100]
工作负载拆分在任何处理之前发生,因此,Stream实现会将范围[1, 101]
拆分为要处理的子范围。 此时,它不知道过滤器将完全删除前半部分,它不能在不评估谓词的情况下知道,并且应该已经并行发生,因此,在工作负载分裂之后。
因此,每个子范围都以相同的方式处理,包括将结果收集到容器中并在之后组合这些容器,即使它们恰好是空的。 规范并没有说当没有元素到达收集器时会跳过组合步骤,因此,你不应该期望这样。 虽然理论上可以跟踪是否有任何元素到达收集器,但这种跟踪只能用于特定情况,并且甚至不清楚是否将容器与空容器组合(如添加空List
或附加空StringBuilder
)比跟踪更昂贵。
当然,没有什么能阻止你优化你的组合器,如果它保留了语义,例如代替(sb1, sb2) -> sb1.append(sb2)
,你可以使用(sb1, sb2) -> sb1.length()==0? sb2: sb1.append(sb2)
(sb1, sb2) -> sb1.length()==0? sb2: sb1.append(sb2)
您可以查看此问答,“Java流并行化的可视化”以获取更多详细信息。
它看起来是一个尝试优化导致一些不必要的StringBuilders被创建来处理x < 51
。 由于过滤器,这些构建器从未累积过字符串,但即使它们是空的,它们仍然与其他构建器连接在一起。 也许通过更智能的优化,可以消除一些工作。
关于你的第二个问题,如果你只想在连接期间交换订单,你会写sb2.append(sb1)
,虽然这会产生不可靠的结果,因为你以不同的顺序附加,并且这种不一致的行为会违反合同。
你在sb1.append("-"+sb2)
破坏了关联性,这是由文档说明的。 因此,在并行执行中,您获得的内容实际上是未知/不可预测的。
正确的combiner
可以是StringBuilder::append
或lambda:
(left, right) -> left.append(right)
它们不会出现故障,它们会保留订单(无论订单是什么)。 例如,如果您从HashSet
(没有任何顺序)流式传输,您将得到不同的结果。 可能使用java-9和Set.of
这个结果会因运行而异。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.