【问题标题】:Java8 stream with memoization带记忆的 Java8 流
【发布时间】:2017-01-19 07:43:55
【问题描述】:

如何在 java8(可能是记忆过程)中重用已经通过流迭代计算的值?

如果流被复制或再次提供,它将被重新计算。在某些情况下,最好用内存换取 CPU 时间。从头开始收集所有内容可能不是一个好主意,因为流用于查找满足谓词的第一个项目。

Stream<Integer> all = Stream.of(1,2,3,4,5, ...<many other values>... ).
      map(x->veryLongTimeToComputeFunction(x));
System.out.println("fast find of 2"+all.filter(x->x>1).findFirst());

//both of these two lines generate a "java.lang.IllegalStateException: stream has already been operated upon or closed"
System.out.println("no find"+all.filter(x->x>10).findFirst());
System.out.println("find again"+all.filter(x->x>4).findFirst());

问题类似于Copy a stream to avoid "stream has already been operated upon or closed" (java 8)

【问题讨论】:

    标签: java java-8 java-stream memoization


    【解决方案1】:

    规范的内存中流源是一个集合。一个简单的、不能并行的流记忆可以实现如下:

    public static void main(String[] args) {
        Supplier<Stream<Integer>> s=memoize(
            IntStream.range(0, 10_000)
                     .map(x -> veryLongTimeToComputeFunction(x))
        );
        System.out.println("First item > 1  "+s.get().filter(x -> x>1 ).findFirst());
        System.out.println("First item > 10 "+s.get().filter(x -> x>10).findFirst());
        System.out.println("First item > 4  "+s.get().filter(x -> x>4 ).findFirst());
    }
    static int veryLongTimeToComputeFunction(int arg) {
        System.out.println("veryLongTimeToComputeFunction("+arg+")");
        return arg;
    }
    
    public static <T> Supplier<Stream<T>> memoize(BaseStream<T,?> stream) {
        Spliterator<T> sp=stream.spliterator();
        class S extends Spliterators.AbstractSpliterator<T> {
            ArrayList<T> mem=new ArrayList<>();
            S() { super(sp.estimateSize(), sp.characteristics()); }
            public boolean tryAdvance(Consumer<? super T> action) {
                return sp.tryAdvance(item -> {
                    mem.add(item);
                    action.accept(item);
                });
            }
        }
        S s=new S();
        return () -> Stream.concat(s.mem.stream(), StreamSupport.stream(s, false));
    }
    

    在向供应商请求下一个 Stream 之前,请注意完成 Stream 处理。

    【讨论】:

    • 谢谢。我会尝试一下,我希望在 java 集合中找到一些东西,或者至少在 guava、apache commons 或其他普遍可用的小型库中找到一些东西。
    • 嗯,你可以把这个放到一个小图书馆里……
    • @Holger 我不明白你为什么要这样做int ix=mem.size(); if(sp.tryAdvance(mem::add)) { action.accept(mem.get(ix)); return true; } 而不是sp.tryAdvance(item -&gt; { mem.add(item); action.accept(item); });?为什么要读取内存(即mem.get(ix))而不是创建接收项目的匿名函数(lambda)?我相信您有充分的理由这样做,但我不知道它是什么,我想知道它。
    • @MiguelGamboa 我想,最初的想法是mem::add 可以在Spliterator 的整个生命周期内被存储和重用,而捕获消费者的 lambda 表达式则不能。由于这在这里无关紧要,因此我使用了您的建议来简化代码。
    【解决方案2】:

    Java 8 流本质上是惰性的。对流执行的操作按垂直顺序进行评估。 您想要达到的目标可以使用以下代码实现:

    Stream.of(1,2,3,4,5, ...<many other values>... )
        .map(x-> veryLongTimeToComputeFunction(x))
        .filter(x-> x > 1)
        .findFirst();
    

    这将确保仅在找不到匹配的第一个元素之前调用veryLongTimeToComputeFunction()。之后,操作将终止。 在最坏的情况下,如果最后一个数字与条件匹配,则会为所有数字调用veryLongTimeToComputeFunction。

    您还可以将并行流与 findAny() 方法结合使用。它将加快性能。

    【讨论】:

      【解决方案3】:

      流并不意味着保存,它们即将处理数据。

      示例:您正在观看 dvd,用 Java 术语来说,dvd 就像一个集合,从您的 dvd 播放器传输到电视的数据是一个流。你不能保存流,但是你可以刻录 cd,用 java 术语收集它。

      还有其他选择:

      • 将流操作或谓词提取/重构为获取流作为参数并返回流的方法
      • 使用缓存框架:例如Spring 中的方法可以使用 @Cacheable 进行注释。第一次调用执行方法,后续调用在定义的时间内从缓存中获取结果
      • 如果您正在为长时间运行的任务寻找非阻塞执行,请查看RxJava

      【讨论】:

      • 如果 java 流不应该被保存,那么一个集合会这样做吗? Scala 流不需要专门的框架就可以做到这一点。
      • @raisercostin 是关于流的 java 方法。在 Java 中,流只是一个功能性的“附加组件”,Scala 被设计为纯粹的功能性。无论如何,由于veryLongTimeToComputeFunction(x) 对cpu 的影响很大,因此通过将“.parallel()”添加到流中,您将获得很多性能。
      【解决方案4】:

      为什么不在veryLongTimeToComputeFunction 中使用memoization?您可以将备忘录缓存作为参数传递给函数。

      【讨论】:

        【解决方案5】:

        我建议将您的 Stream 收集到一个列表中,然后在列表的流上运行您的过滤器。

        【讨论】:

        • 但这将对所有字段执行veryLongTimeToComputeFunction,只是在最后保留第一个。
        猜你喜欢
        • 2014-12-05
        • 2019-10-15
        • 1970-01-01
        • 2017-08-15
        • 2019-12-13
        • 1970-01-01
        • 1970-01-01
        • 2013-11-04
        • 2021-04-23
        相关资源
        最近更新 更多