【问题标题】:Clean Up After a Java8 Stream在 Java8 流之后清理
【发布时间】:2017-04-30 04:12:12
【问题描述】:

当我从无限制的 Stream 开始并使用 limit() 方法对其进行限制时,一旦达到限制,我该如何清理 Stream 使用的资源?例如,如果我尝试执行 Files.lines 方法所做的事情,但使用浮点数而不是字符串,我会编写一个看起来像这样的函数:

public static Stream<Float> floats(File f) throws FileNotFoundException {
        FileInputStream fis = new FileInputStream(f);
        return Stream.generate(() -> {

            byte[] buff = new byte[4];
            try { fis.read(buff); }
            catch (Exception e) { e.printStackTrace(); }
            return ByteBuffer.wrap(buff).getFloat(0);

        }).limit(f.length()/4);
    }

这应该会流式传输一个充满浮点数的大型二进制文件,直到我到达文件末尾。我将文件的长度(以字节为单位)除以构成浮点数的四个字节。但是,一旦达到此限制,我想找到某种方法来执行 fis.close() 。 Streams API 有什么方法可以让您这样做吗?

【问题讨论】:

  • 可以提供一个关闭 fisonClose 并记录您的方法,说它返回一个必须关闭的 Stream
  • 你的异常处理是有问题的。

标签: java stream java-8 java-stream


【解决方案1】:

我想出了另一种可能性。这有点像黑客,但它似乎有效。我只是通过一个始终返回 true 的过滤器运行 Stream,但在此过程中,它会检查 EOF 并在到达时关闭输入流。我希望 API 中有一种更简洁的方法来执行此操作。

    Stream<Float> answer;
    FileInputStream fis = new FileInputStream(f);

    answer = Stream
            .generate(() -> {
                byte[] buff = new byte[4];
                try { fis.read(buff); }
                catch (Exception e) { e.printStackTrace(); }
                return ByteBuffer.wrap(buff).getFloat(0);})
            .filter(x -> { 
                try { if (fis.available() == 0) fis.close(); } 
                catch (Exception e) { e.printStackTrace(); }
                return true;})
            .limit(f.length()/4);

    return answer;

【讨论】:

  • 你为什么不按照 Sotirios 的建议使用 onClose()?为什么你接受了拉斐尔的回答,这显然是错误的?
  • 当我接受它时我没有意识到它是错误的,并且 onClose 方式可以工作,但我不想创建一个新类,其中包含需要在之后调用的附加方法流达到其极限。我想要一个在内部负责清理的 Stream 对象。
  • 你为什么需要写另一个类? stream.onClose(() -&gt; { try { fis.close(); } catch(IOException e) { throw new UncheckedIOException(e); } })。顺便说一句, InputStream.available() 没有做你认为它做的事情。你永远不应该使用那种方法。
  • 顺便说一句,我之前评论中的代码正是 Files.lines() 使用的。
  • 这意味着您在完成后忘记关闭流,在代码中调用您的方法并使用流。
【解决方案2】:

我会使用完全不同的方法:

public static Stream<Float> floats(File f) throws FileNotFoundException {
    try(FileChannel fch = FileChannel.open(f.toPath(), StandardOpenOption.READ)) {
        ByteBuffer  bb = fch.map(FileChannel.MapMode.READ_ONLY, 0, fch.size());
        FloatBuffer fb = bb.asFloatBuffer();
        return IntStream.range(0, fb.remaining()).mapToObj(fb::get);
    }
    catch(IOException ex) { // or consider to declare "throws IOException" instead
        FileNotFoundException fnfe = new FileNotFoundException(ex.getMessage());
        fnfe.initCause(ex);
        throw fnfe;
    }
}

这样,FileChannel 在方法返回时已经关闭。它也可能更有效。请注意,对于很多操作,将数据处理为DoubleStream 可能会更有效,即使源值为floats 并且仅将最终结果的类型缩小回float,如有必要:

public static DoubleStream floatsAsDouble(File f) throws FileNotFoundException {
    try(FileChannel fch = FileChannel.open(f.toPath(), StandardOpenOption.READ)) {
        ByteBuffer  bb = fch.map(FileChannel.MapMode.READ_ONLY, 0, fch.size());
        FloatBuffer fb = bb.asFloatBuffer();
        return IntStream.range(0, fb.remaining()).mapToDouble(fb::get);
    }
    catch(IOException ex) { // or consider to declare throwsIOException instead
        FileNotFoundException fnfe = new FileNotFoundException(ex.getMessage());
        fnfe.initCause(ex);
        throw fnfe;
    }
}
…
    float sum=(float)floatsAsDouble(new File("path")).sum();

另外,您可以考虑直接使用Paths 而不是File

【讨论】:

  • 这两个不会在调用 return 时立即关闭文件通道吗?那岂不是让fb::get 在关闭的资源上运行,因此不起作用?
  • @M. Prokhorov:甚至在我的回答中也说过:“这样,当方法返回时,FileChannel 已经关闭”。这是为了避免以后不得不关闭任何东西。重要的一点可以在documentation of FileChannel.map 中找到:“映射一旦建立,就不再依赖于用于创建它的文件通道。尤其是关闭通道对映射的有效性没有影响。
  • 我明白了。所以它会创建一个 ByteBuffer 指向 Channel 指向的任何地方,就像磁盘上的地方一样,对吗?
  • @M. Prokhorov:它与通道和作为参数传递给map 的范围表示的基础文件相同。内存区域如何与文件同步取决于实现,但通常,由于对文件的所有操作都经过操作系统,因此映射的内存与系统的 I/O 缓冲区相同,并将立即反映所有更改。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-06-29
  • 2018-12-23
  • 1970-01-01
  • 2014-11-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多