Java - 毫无例外地读取大文件(几GB)

时间:2017-05-04 11:52:24

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

这个问题非常简短。 我有一个文件

Datei.trec-3,99 GB ,我用以下代码阅读:

orient

这是输出:

public class Main {
    public static void main(String[] args) {
        byte[] content = null;
        try {
            content = Files.readAllBytes(Paths.get("D:", "Videos","Captures","Datei.trec"));
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println(content);
    }
}

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

2 个答案:

答案 0 :(得分:4)

问题是保存所有数据所需的数组大于MAX_BUFFER_SIZEjava.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?

不要这样做。

答案 1 :(得分:0)

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

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