Java - 读取大文件(几 GB)

Java - Reading a big file (few GB)


这个问题很短。 我有一个大小为 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)

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

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

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

问题是保存所有数据所需的数组大于 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 提供了很多 classes 来做到这一点:InputStreamReaderScanner 等——它们在大多数早期讨论过介绍性 Java 课程和书籍。研究其中之一。

示例

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

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

我注意到您的文件是 .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 字节。访问内容比使用简单的数组稍微复杂一些——但实现细节可以隐藏在 class.

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

不要这样做。