【问题标题】:Fastest way to incrementally read a large file增量读取大文件的最快方法
【发布时间】:2016-04-10 21:36:48
【问题描述】:

当给定一个 MAX_BUFFER_SIZE 的缓冲区,以及一个远远超过它的文件时,如何:

  1. 以 MAX_BUFFER_SIZE 块读取文件?
  2. 尽快完成

我尝试使用 NIO

    RandomAccessFile aFile = new RandomAccessFile(fileName, "r");
    FileChannel inChannel = aFile.getChannel();

    ByteBuffer buffer = ByteBuffer.allocate(CAPARICY);

    int bytesRead = inChannel.read(buffer);

    buffer.flip();

        while (buffer.hasRemaining()) {
            buffer.get();
        }

        buffer.clear();
        bytesRead = inChannel.read(buffer);

    aFile.close();

和常规 IO

    InputStream in = new FileInputStream(fileName);

    long length = fileName.length();

    if (length > Integer.MAX_VALUE) {
        throw new IOException("File is too large!");
    }

    byte[] bytes = new byte[(int) length];

    int offset = 0;

    int numRead = 0;

    while (offset < bytes.length
            && (numRead = in.read(bytes, offset, bytes.length - offset)) >= 0) {
        offset += numRead;
    }

    if (offset < bytes.length) {
        throw new IOException("Could not completely read file " + fileName);
    }

    in.close();

事实证明,常规 IO 在做同样事情时的速度大约是 NIO 的 100 倍。我错过了什么吗?这是预期的吗?有没有更快的方法来读取缓冲区块中的文件?

最终,我正在处理一个大文件,我没有内存可以一次读取所有文件。相反,我想以块的形式逐步阅读它,然后用于处理。

【问题讨论】:

  • NIO 不一定更快,只是不同而已。如果java.io 对你来说更快,那么忽略 NIO。
  • 没有直接字节缓冲区的 NIO 是没用的(或者至少在 Linux 上是 transferTo,在 Windows 上它是模拟的,因此没用)
  • @skaffman,NIO 在正确使用时(严格)更快,与常规 IO 相比,它确实避免了缓冲区复制。不过,对于新手来说,它并不是很容易使用。
  • 我只想指出您的两个 impl 正在做不同的事情。示例中的 NIO 代码正在将字节读取到 ByteBuffer 中,然后您从 ByteBuffer 中的后备字节 [] 中再次 逐一读取它们,而在此期间什么也不做-环形。在 IO 代码中,您将字节读入 byte[] 并且不做其他工作。您的 NIO 代码正在执行 2 倍的读取操作以及数十亿次 get() 调用以获取单个字节值。

标签: java file-io nio


【解决方案1】:

如果你想让你的第一个例子更快

FileChannel inChannel = new FileInputStream(fileName).getChannel();
ByteBuffer buffer = ByteBuffer.allocateDirect(CAPACITY);

while(inChannel.read(buffer) > 0)
    buffer.clear(); // do something with the data and clear/compact it.

inChannel.close();

如果你想让它更快。

FileChannel inChannel = new RandomAccessFile(fileName, "r").getChannel();
MappedByteBuffer buffer = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, inChannel.size());
// access the buffer as you wish.
inChannel.close();

对于最大为 2 GB 的文件,这可能需要 10 到 20 微秒。

【讨论】:

  • 不要忘记关闭 RandomAccessFile,因为它是资源泄漏。
  • @crush 真,关闭文件通道关闭随机访问文件
  • 如何使用MappedByteBuffer 读取整行,而不是逐字符读取?见howtodoinjava.com/2013/05/01/…
  • 刚看了源码,发现关闭文件通道并没有关闭随机访问文件,而是反过来,所以一定要关闭RandomAccessFile :)
【解决方案2】:

假设您需要一次将整个文件读入内存(就像您目前正在做的那样),那么读取较小的块和 NIO 都不会在这里为您提供帮助。

事实上,您可能最好阅读较大的块 - 您的常规 IO 代码会自动为您执行。

您的 NIO 代码当前速度较慢,因为您一次只能读取一个字节(使用 buffer.get();)。

如果你想分块处理——例如,在流之间传输——这是一种不使用 NIO 的标准方法:

InputStream is = ...;
OutputStream os = ...;

byte buffer[] = new byte[1024];
int read;
while((read = is.read(buffer)) != -1){
    os.write(buffer, 0, read);
}

这使用的缓冲区大小仅为 1 KB,但可以传输无限量的数据。

(如果您将答案扩展为您在功能级别上实际想要做的事情的详细信息,我可以进一步改进它以获得更好的答案。)

【讨论】:

  • 以块的形式处理数据以便以后在流之间传输正是我使用此代码的目的。做这样的事情,你还会为 nio 操心吗?
  • @JAM - 不,除非使用其他已经使用 NIO 来实现适当功能的 API - 例如。如果处理许多并发文件,并且需要避免大量的多线程。
  • 最后,你能推荐一下如何使用 nio 一次读取n 字节吗?只是想知道
  • @JAM - 类似于我的回答中的非 NIO 示例,在您的 NIO 示例中,inChannel.read(buffer); - 只需使用适当大小的缓冲区。您不是要读取整个文件,而是要读取一个块。请注意,这是一个非阻塞调用,因此您获得的字节数可能少于您要求的字节数。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-04-07
  • 1970-01-01
  • 2015-11-23
  • 1970-01-01
  • 2014-11-08
  • 2012-08-26
相关资源
最近更新 更多