【问题标题】:Java - Reading a big file (few GB)Java - 读取一个大文件(几 GB)
【发布时间】:2017-10-02 14:10:14
【问题描述】:

这个问题很短。 我有一个大小为 4GB 的文件,目前我确实使用以下代码阅读它:
public class Main {
    public static void main(String[] args) {
        byte[] content = null;
        try {
            content = Files.readAllBytes(Paths.get("/path/to/file.ext"));
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println(content);
    }
}

这是输出:

Exception in thread "main" java.lang.OutOfMemoryError: Required array size too large
    at java.nio.file.Files.readAllBytes(Unknown Source)
    at Main.main(Main.java:13)

有没有一种方法可以毫无例外地读取数组(流等)? 该文件小于允许的 HEAP,因此应该可以在程序中一次存储所有数据。

【问题讨论】:

  • content 是一个大小有限的数组,你正在溢出......
  • 我知道但是我应该如何阅读内容呢?
  • 你会流式传输它。读取一点数据,用它做你需要的,读取下一点等
  • 我需要在一个变量(数组或数据类型)中收集全部内容
  • 在这种情况下,您将不得不手动执行此操作并拆分您的内容。例如。读取 MAX_BUFFER_SIZE,将其写入数组 1,创建第二个数组并将其余部分读取到该数组中

标签: java out-of-memory java-stream file-read


【解决方案1】:

问题在于保存所有数据所需的数组大于MAX_BUFFER_SIZE,在java.nio.Files 中定义为Integer.MAX_VALUE - 8

public static byte[] readAllBytes(Path path) throws IOException {
        try (SeekableByteChannel sbc = Files.newByteChannel(path);
             InputStream in = Channels.newInputStream(sbc)) {
            long size = sbc.size();
            if (size > (long)MAX_BUFFER_SIZE)
                throw new OutOfMemoryError("Required array size too large");

            return read(in, (int)size);
        }
    }

这是必要的,因为数组是由整数索引的 - 这是您可以获得的最大数组。

您有三个选择:

通过文件流式传输

也就是说,打开文件,读取一个块,处理它,读取另一个块,处理它,一次又一次,直到你完成整个过程。

Java 提供了很多类来做到这一点:InputStreamReaderScanner 等等——它们在大多数介绍性 Java 课程和书籍的早期都有讨论。研究其中之一。

例如https://stackoverflow.com/a/21706141/7512

这取决于您能够在文件的早期部分做一些有价值的事情,而无需知道接下来会发生什么。很多时候都是这种情况。其他时候,您必须多次通过文件。

文件格式通常被设计成可以一次性完成处理 - 考虑到这一点,设计自己的文件格式是个好主意。

我注意到您的文件是一个.trec 文件,它是一个截屏视频。视频和音频格式特别有可能是为流式传输而设计的 - 这就是您可以在下载结束之前观看 YouTube 视频开头的原因。

内存映射

如果你真的需要跳转文件的内容来处理它,你可以将它作为一个内存映射文件打开。

查看RandomAccessFile 的文档 - 这为您提供了一个带有seek() 方法的对象,因此您可以读取文件数据中的任意点。

读取多个数组

我仅出于完整性考虑才包含此内容;将整个文件吞入堆内存是很难看的。但如果你真的想要,你可以将字节存储在多个数组中——也许是List<byte[]>。 Java-ish 伪代码:

  List<byte[]> filecontents = new ArrayList<byte[]>();
  InputStream is = new FileInputStream(...);
  byte[] buffer = new byte[MAX_BUFFER_SIZE];
  int bytesGot = readUpToMaxBufferSizeFrom(file);
  while(bytesGot != -1) {
       byte[] chunk = new byte[bytesGot];
       System.arrayCopy(buffer, 0, chunk, 0, bytesGot);
       filecontents.add(chunk);
  }

这允许您最多 MAX_BUFFER_SIZE * Integer.MAX_INTEGER 字节。访问内容比使用简单的数组稍微复杂一些——但实现细节可以隐藏在类中。

当然,您需要将 Java 配置为拥有巨大的堆大小 - 请参阅 How to set the maximum memory usage for JVM?

别这样。

【讨论】:

  • 抱歉,增加堆大小无济于事——已删除该部分并解释了MAX_BUFFER_SIZE
【解决方案2】:

我建议您通过文件流式传输;例如,您可以使用来自 Apache Commons 的 LineIterator:

LineIterator it = FileUtils.lineIterator(theFile, "UTF-8");
try {
    while (it.hasNext()) {
        String line = it.next();
    }
} finally {
    LineIterator.closeQuietly(it);
}

【讨论】:

  • 我需要在一个变量(数组或数据类型)中收集全部内容
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-03-13
  • 2013-02-03
  • 2012-01-05
  • 1970-01-01
  • 2022-01-02
  • 2015-04-24
  • 1970-01-01
相关资源
最近更新 更多