【问题标题】:Java 8's Files.lines(): Performance concern for very long lineJava 8 的 Files.lines():非常长的行的性能问题
【发布时间】:2017-10-11 05:28:46
【问题描述】:

Java 8 的流 API 已经很方便并广受欢迎。对于文件 I/O,我发现提供了两个 API 来生成流输出:Files.lines(path)bufferedReader.lines()

不过,我没有找到提供固定大小缓冲区的 Stream 用于读取文件的流 API。

我担心的是:如果文件的行很长,例如4GB 文件只有一行,这些基于行的 API 是不是非常低效?

基于行的阅读器至少需要4GB 内存来保持该行。 与固定大小的缓冲区读取器 (fileInputStream.read(byte[] b, int off, int len)) 相比,它最多占用内存的缓冲区大小。

如果上述问题属实,是否有更高效的文件 i/o API 的 Stream API?

【问题讨论】:

  • Files.lines(path)bufferedReader.lines() 用于读取字符/字符串,而 InputStream::read 方法用于读取字节。我不知道你的问题出在哪里。
  • 如果输入是基于行的,并且流链可以单独处理每一行,那么如何在固定大小的块中处理相同的数据?

标签: java io stream


【解决方案1】:

如果您有一个只有一行的 4GB 文本文件,并且您正在“逐行”处理它,那么您在编程中犯了一个严重错误,因为您不了解正在处理的数据.

当您需要使用 CSV 或其他此类格式的数据进行简单的工作时,它们是一种方便的方法,并且线条大小是可管理的。

具有单行的4GB 文本文件的真实示例是没有换行符的 XML 文件。您将使用流式 XML 解析器来读取它,而不是滚动您自己的逐行读取的解决方案。

【讨论】:

  • 我知道基于行的 API 不适合这种情况。这就是为什么我首先要求更好的 API。您能否举一个具有 maven 依赖关系的“流式 XML 解析器”的示例?
  • 是的,StAX。您也可以查找 SAX 和 DOM,并比较差异。
【解决方案2】:

这取决于您希望如何处理数据,哪种交付方式合适。因此,如果您的处理需要逐行处理数据,则没有办法这样做。

如果你真的想要固定大小的字符数据块,你可以使用以下方法:

public static Stream<String> chunks(Path path, int chunkSize) throws IOException {
    return chunks(path, chunkSize, StandardCharsets.UTF_8);
}
public static Stream<String> chunks(Path path, int chunkSize, Charset cs)
throws IOException {
    Objects.requireNonNull(path);
    Objects.requireNonNull(cs);
    if(chunkSize<=0) throw new IllegalArgumentException();

    CharBuffer cb = CharBuffer.allocate(chunkSize);
    BufferedReader r = Files.newBufferedReader(path, cs);
    return StreamSupport.stream(
        new Spliterators.AbstractSpliterator<String>(
            Files.size(path)/chunkSize, Spliterator.ORDERED|Spliterator.NONNULL) {
            @Override public boolean tryAdvance(Consumer<? super String> action) {
                try { do {} while(cb.hasRemaining() && r.read(cb)>0); }
                catch (IOException ex) { throw new UncheckedIOException(ex); }
                if(cb.position()==0) return false;
                action.accept(cb.flip().toString());
                return true;
            }
    }, false).onClose(() -> {
        try { r.close(); } catch(IOException ex) { throw new UncheckedIOException(ex); }
    });
}

但如果您的下一个问题是“我如何合并相邻的流元素”,我不会感到惊讶,因为这些固定大小的块很少是您实际任务的自然数据单元。

通常,后续步骤是在内容中执行模式匹配,在这种情况下,最好首先使用Scanner,它能够在流式传输数据时执行模式匹配,可以是高效完成,因为正则表达式引擎告诉缓冲更多数据是否会改变匹配操作的结果(参见hitEnd()requireEnd())。不幸的是,从 Scanner 生成匹配流仅添加到 Java 9 中,但请参阅 this answer 以了解该功能到 Java 8 的反向移植。

【讨论】:

  • 谢谢。我一直在寻找编写自己的流生成 API 的入门指南。我认为这个答案是一个正确的起点。我还没有到找到相邻元素的阶段。但是,当它出现时,最好有一个可以在哪里寻找的刺激标志。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-02-04
  • 2020-06-10
  • 2017-07-27
  • 2016-02-16
  • 2014-09-20
  • 2014-12-04
相关资源
最近更新 更多