Java流 - 收集组合器

7
为什么以下代码会出现问题:
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

第一部分(------------------)不应该包含在输出中吗?

另外,我了解到 collect 中的 combiner 可能会被无序调用,因此可能会出现 :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]

你期望的输出是什么? - Bentaye
1
顺便提一下:您不需要过滤器,只需执行.range(51, 101)即可。 - Bentaye
2
如果没有那个过滤器,------------------ 部分就不会出现在输出中,也不会有人问这个问题 :) - Eran
@Eran 这只是一条评论,不是答案 :) - Bentaye
@Eran 对的,但这将发生在任何类型(StringBuilder)中。对于“过滤”掉的元素,总会有来自Supplier生成和合并的值。顺便问一下,你知道以前是否有人问过这个问题吗?似乎这是一个非常有趣的问题,也是为什么会这样发生的原因。 - Eugene
显示剩余2条评论
3个回答

4
工作负载分割发生在任何处理之前,因此,Stream实现将把范围[1, 101]拆分为子范围进行处理。此时,它不知道过滤器将完全删除前一半,除非评估谓词,这应该已经并行发生,因此在工作负载分割后。

因此,每个子范围都以相同的方式进行处理,包括将结果收集到容器中,并在之后组合这些容器,即使它们恰好为空。规范没有说当没有元素到达收集器时将跳过组合步骤,因此您不应该期望那样。虽然理论上可以追踪是否有任何元素到达收集器,但这种追踪只能服务于特定情况,甚至不清楚将一个容器与空容器组合(例如添加空的 List 或附加空的 StringBuilder)是否比这种追踪更昂贵。

当然,如果保留语义,您可以优化组合器,例如,可以使用(sb1, sb2) -> sb1.length()==0? sb2: sb1.append(sb2)而不是(sb1, sb2) -> sb1.append(sb2)

您可以查看这个问题和答案,“Java Stream并行化的可视化”以获取更多详细信息。


这似乎是一个合乎逻辑的解释。您能否告诉我,如果我使用了 sb1.append("-"+sb2)(如下面的注释中),是否违反了结合律规则? - Bojan Vukasovic
1
仅凭“结合律”是无法解释的。Collector文档中指出:“为确保顺序和并行执行产生等效结果,收集器函数必须满足标识结合律约束。——标识约束表示对于任何部分累积的结果,将其与空结果容器组合必须产生等效结果。” - Holger
谢谢。我使用您提供的代码更新了问题,并生成了一棵树。 - Bojan Vukasovic

2

您在sb1.append("-"+sb2)中破坏了结合性, 文档已经说明了这一点。因此,在并行执行时,您得到的结果是未知/不可预测的。

一个正确的combiner示例可以是 StringBuilder::append 或者使用 lambda 表达式:

(left, right) -> left.append(right)

它们不能无序,它们将保留顺序(无论是什么顺序)。例如,如果你从一个 HashSet (它没有任何顺序)进行流处理,你将得到不同的结果。可能会使用java-9和 Set.of,每次运行这个结果都会有所不同。


我猜我错了,请问您能否解释一下结合律的逻辑是否错误:A op B op C op D == (A op B) op (C op D);第一种情况:'a-b''a-b-c''a-b-c-d';第二种情况:'a-b''c-d''a-b-c-d' - Bojan Vukasovic
这个回答完全错了重点。当然他打破了结合律……他的代码整个目的就是为了暴露内部工作原理,这是通常不应该做的。 - Patrick Parker
@PatrickParker :) 当然了?如果结合律的逻辑有误,请解释一下 有印象吗?此外,如果你的答案侧重于“内部工作”,那么我可能错过了它们。你可能需要扩展一下,解释为什么首先要合并空的StringBuilders。 - Eugene
@Eugene 抱歉你觉得我的评论没有帮助,但请遵守适当的礼仪。如果其他回答存在问题,你应该在那里发表评论。我坚持我在这里写的内容;这不是小气,而是对这个答案完全没有理解的有效批评。详情请查看被接受的答案。 - Patrick Parker
@PatrickParker,抱歉如果让你觉得我在发牢骚,那不是我的本意。我的观点是这个并不是关联性的,并且请注意identity是通过StringBuilder::new强制执行的。答案的观点是为了支持正确的顺序/并行结果,需要同时具有关联性和身份特征。 - Eugene

2
看起来尝试优化导致创建了一些不必要的StringBuilders来处理“x<51”。由于过滤器,这些构建器从未累积字符串,但即使它们为空,它们仍然被连接到其他字符串上。也许通过更智能的优化,可以消除其中的一些工作。
关于您的第二个问题,如果您只想在连接期间交换顺序,则应编写“sb2.append(sb1)”,尽管这会创建不可靠的结果,因为您是按不同顺序进行附加的,而这种不一致的行为将违反协议。

对于第二个,你将会违反合同(根据文档),这只适用于不关心顺序的内容,比如 Set - Eugene
2
@FedericoPeraltaSchaffner 哦?尝试将范围更改为 51,101 或尝试删除筛选器。任何更改都将消除前导连字符。 - Patrick Parker
@PatrickParker 这就像是将元素并行放置到一个 ArrayList 中,然后尝试理解什么出了问题以及在哪里出了问题。 - Eugene

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接