当前位置: 首页 > 面试题库 >

可以在顺序流上使用收集器的合并器功能吗?

戈曾琪
2023-03-14
问题内容

示例程序:

public final class CollectorTest
{
    private CollectorTest()
    {
    }

    private static <T> BinaryOperator<T> nope()
    {
        return (t, u) -> { throw new UnsupportedOperationException("nope"); };
    }

    public static void main(final String... args)
    {
        final Collector<Integer, ?, List<Integer>> c
            = Collector.of(ArrayList::new, List::add, nope());

        IntStream.range(0, 10_000_000).boxed().collect(c);
    }
}

因此,为简化起见,没有最终转换,因此生成的代码非常简单。

现在,IntStream.range()产生一个顺序流。我只是将结果装箱到Integers中,然后Collector将其收集到中List<Integer>。很简单

而且,无论我运行此示例程序多少次,都UnsupportedOperationException不会成功,这意味着永远不会调用我的虚拟组合器。

我有点期望,但是后来我已经误解了流,以至于我不得不问这个问题…

可以将Collector的组合时,流过被称为 保证 是连续的?


问题答案:

仔细阅读ReduceOps.java中的流实现代码后发现,只有在ReduceTask完成时才调用Combine函数,并且ReduceTask仅在并行评估管道时才使用实例。因此,
在当前实现中, 在评估顺序管道时永远不会调用组合器。

但是,规范中没有任何东西可以保证这一点。A
Collector是一个对其实现有要求的接口,并且顺序流没有授予任何豁免。我个人很难想象为什么顺序管道评估可能需要调用合并器,但是比我想象更多的人可能会发现它的巧妙用法并实现了它。规范允许这样做,即使今天的实现不支持它,您仍然必须考虑它。

这不足为奇。流API的设计中心是在顺序执行的基础上支持并行执行。当然,程序可以观察它是顺序执行还是并行执行。但是API的设计是要支持一种允许的编程风格。

如果您正在编写一个收集器,但发现写一个联合组合器函数是不可能的(不便,困难或困难),导致您想将流限制为顺序执行,这可能意味着您走错了方向。是时候退后一步,考虑以另一种方式解决问题了。

不需要关联组合器功能的常见归约样式操作称为 fold-left 。主要特征是折叠功能严格从左到右应用,一次执行一次。我不知道并行左折的方法。

当人们试图以我们一直在谈论的方式扭曲收藏家时,他们通常会在寻找诸如左折之类的东西。Streams
API对此操作没有直接API支持,但是编写起来很容易。例如,假设您要使用此操作来减少字符串列表:重复第一个字符串,然后追加第二个字符串。很容易证明此操作不具有关联性:

List<String> list = Arrays.asList("a", "b", "c", "d", "e");

System.out.println(list.stream()
    .collect(StringBuilder::new,
             (a, b) -> a.append(a.toString()).append(b),
             (a, b) -> a.append(a.toString()).append(b))); // BROKEN -- NOT ASSOCIATIVE

按顺序运行,将产生所需的输出:

aabaabcaabaabcdaabaabcaabaabcde

但是,当并行运行时,它可能会产生以下内容:

aabaabccdde

由于它是按顺序“工作”的,因此我们可以通过调用来强制执行此操作,sequential()并通过使组合器抛出异常来对此进行备份。此外,供应商必须被准确地调用一次。无法合并中间结果,因此,如果两次致电供应商,我们就会遇到麻烦。但是由于我们“知道”供应商在顺序模式下仅被调用一次,所以大多数人不必为此担心。实际上,我已经看到人们写“供应商”来违反供应商合同,返回一些现有对象而不是创建一个新对象。

通过使用3-arg形式的collect(),我们在打破合同的三个函数中有两个。这不应该告诉我们以不同的方式做事吗?

此处的主要工作由累加器功能完成。要完成折叠样式的缩小,我们可以使用严格按从左到右的顺序应用此功能forEachOrdered()。我们必须在前后进行一些设置和整理代码,但这没问题:

StringBuilder a = new StringBuilder();
list.parallelStream()
    .forEachOrdered(b -> a.append(a.toString()).append(b));
System.out.println(a.toString());

自然地,尽管并行运行的性能优势可能会因的订购要求而被否定,但并行运行仍然可以正常工作forEachOrdered()

总而言之,如果您发现自己想进行可变的归约但缺少关联的组合器功能,则将您的流限制为顺序执行,将问题 重折叠为左折
运算并forEachRemaining()在累加器函数上使用。



 类似资料:
  • 示例程序: 所以,为了简化这里的问题,没有最终的转换,所以得到的代码非常简单。

  • 在jdk8中,Stream提供带有、和的功能 我在注释中看到了将String Stream转换为String的示例,但我很困惑组合器在函数签名中的用途。我认为在累加器中,新元素已添加到结果大陆? 我尝试给组合器一个null值,但我得到了一个null指针异常。

  • JavaAPI文档声明方法的参数必须是: 用于组合两个值的关联、非干扰、无状态函数,必须与累加器函数兼容 合路器是双余弦 例如,根据组合顺序,以下示例可能给出不同的结果:或m2。addAll(m1)。 我知道,在这种情况下,我们可以简单地使用方法句柄,例如ArrayList::addAll。然而,在某些情况下,需要Lambda,我们必须按正确的顺序组合项目,否则在并行处理时可能会得到不一致的结果。

  • 问题内容: 在上面的代码中,我可以期望中的名称顺序始终与中的顺序相同吗? 问题答案: 是的,只要您没有将并行流显式转换为模式,即使您正在使用并行流,也可以期望这样做。 顺序永远不会在顺序模式下更改,但可能会在并行模式下更改。流变得无序: 如果您通过调用将其明确转换为无序模式 如果流源报告它是无序的(例如,流是无序的,因为顺序取决于实现,因此您不能依赖它) 如果您使用的是无序终端操作(例如,进行操作

  • 我有两个列表,我想连接成一个列表,然后使用< code>Collectors.toMap来删除基于特定字段的重复项。同一列表内不能有重复,只能在列表之间。 当谈到解决具有相同键的值的冲突时,我希望对第一个列表中的项目进行优先级排序。 我可以提供<代码>(a,b)- 参数是否保证以与列表相同的顺序传递?