Java - 读取大文件(几 GB)

Nit*_*ton -6 java out-of-memory file-read java-stream


这个问题很短。我有一个大小为 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

这是输出:

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)
Run Code Online (Sandbox Code Playgroud)

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

sli*_*lim 5

问题是保存所有数据所需的数组大于MAX_BUFFER_SIZE,定义java.nio.FilesInteger.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);
        }
    }
Run Code Online (Sandbox Code Playgroud)

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

您有三个选择:

通过文件流式传输

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

Java提供了大量的类来做到这一点:InputStreamReaderScanner等等-他们早在大多数入门的Java课程和书籍讨论。研究其中之一。

示例/sf/answers/1519429901/

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

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

我注意到你的文件是一个.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);
  }
Run Code Online (Sandbox Code Playgroud)

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

当然,您需要将 Java 配置为具有巨大的堆大小 - 请参阅如何设置 JVM 的最大内存使用量?

不要这样做。