在Windows系统中处理大文件时使用FileChannel.transferTo方法

10

使用Java NIO可以更快地复制文件。我在互联网上找到了主要有两种方法来完成这个任务。

public static void copyFile(File sourceFile, File destinationFile) throws IOException {
    if (!destinationFile.exists()) {
        destinationFile.createNewFile();
    }

    FileChannel source = null;
    FileChannel destination = null;
    try {
        source = new FileInputStream(sourceFile).getChannel();
        destination = new FileOutputStream(destinationFile).getChannel();
        destination.transferFrom(source, 0, source.size());
    } finally {
        if (source != null) {
            source.close();
        }
        if (destination != null) {
            destination.close();
        }
    }
}

20个非常有用的Java代码片段中,我发现了一个不同的注释和技巧:

public static void fileCopy(File in, File out) throws IOException {
    FileChannel inChannel = new FileInputStream(in).getChannel();
    FileChannel outChannel = new FileOutputStream(out).getChannel();
    try {
        // inChannel.transferTo(0, inChannel.size(), outChannel); // original -- apparently has trouble copying large files on Windows
        // magic number for Windows, (64Mb - 32Kb)
        int maxCount = (64 * 1024 * 1024) - (32 * 1024);
        long size = inChannel.size();
        long position = 0;
        while (position < size) {
            position += inChannel.transferTo(position, maxCount, outChannel);
        }
    } finally {
        if (inChannel != null) {
            inChannel.close();
        }
        if (outChannel != null) {
            outChannel.close();
        }
    }
}

但我没找到或理解什么是

"Windows的魔法数字,(64Mb-32Kb)"

它说inChannel.transferTo(0, inChannel.size(), outChannel)在Windows上有问题,32768(=(64*1024*1024)-(32*1024))字节对于这种方法来说是最优的。

3个回答

12

Windows对最大传输大小有硬限制,如果超过了这个限制,你将得到一个运行时异常。因此,你需要进行调整。第二个版本更加优秀,因为它不假设文件在一个transferTo()调用中完全传输,这与Javadoc相符。

将传输大小设置为超过1MB几乎没有意义。

编辑 你的第二个版本存在一个缺陷。每次传输后,你应该递减size。应该更像下面这样:

while (size > 0) { // we still have bytes to transfer
    long count = inChannel.transferTo(position, size, outChannel);
    if (count > 0)
    {
        position += count; // seeking position to last byte transferred
        size-= count; // {count} bytes have been transferred, remaining {size}
    }
}

3
你能否详细解释一下“将传输大小设置为超过约1MB基本上是毫无意义的”?传输大小是否与传输速率有关?影响文件传输的因素有哪些(特别是在Java中)?回答:能否详细解释“将传输大小设置为超过约1MB基本上是毫无意义的”?传输大小是否与传输速率有关?影响文件传输的因素有哪些(特别是在Java中)?可以,将传输大小设置为超过约1MB基本上是没有意义的,因为这通常不会对文件传输速度产生任何显著影响。传输大小和传输速率有一定的相关性,但不是唯一的因素。在Java中,影响文件传输的因素可能包括网络带宽、文件大小、计算机处理能力等。 - Tapas Bose
1
为什么应该将 maxCount 减少? - Dante WWWW
1
据我所知,只有在一开始将maxCount赋值为文件大小时才应该将其减少:maxCount = filesize,然后在每个循环中递减它。 - Dante WWWW
1
请再次阅读代码。size不是剩余要转移的字节数。size表示输入的结尾。position的值需要不断前进,直到达到最大位置值size。 因此,sizeposition可有的最大值,因为那时你已经到了文件的结尾。它是EOF位置。 它的值始终保持不变。我不确定我是否已经表述清楚。你说的是 - “不断朝着目标前进,并将目标同时向前移动”。这会重复计算你的进度。 - Ajoy Bhatia
1
哦,我明白你的意思了——FileChannel::transferTo 的第二个参数确实是要传输的字节数,所以应该将其减少。然而,在这种情况下,你的代码仍然存在错误。while 循环的条件应该是 while (size > 0),也就是说只要还有字节需要传输就继续循环。将起始点的值 position 与还需传输的字节数进行比较是错误的。因此,while (position < size) 是错误的,因为一旦越过中间点,position > size,所以 while 循环就会退出。 - Ajoy Bhatia
显示剩余11条评论

0

我已经阅读过,这是为了与Windows 2000操作系统兼容。

来源:http://www.rgagnon.com/javadetails/java-0064.html

引用:在win2000中,transferTo()不会传输大于2^31-1字节的文件。它会抛出一个异常"java.io.IOException: Insufficient system resources exist to complete the requested service is thrown." 解决方法是每次循环复制64Mb,直到没有更多数据。


不。您的链接显示该限制适用于整个Windows平台。Windows 2000仅被提及为测试平台。 - user207421
好的,我已经在我的Windows 8平台上测试了transferTo函数,包括循环和不循环两种情况,并且只有时间差异(确实循环更快,但是如果不知道为什么它更快,我就不能在我的项目中使用它)。然而,测试的结果是,无论文件大小超过2GB,两种情况下都能成功完成。我找不到你所说的“Windows对最大传输大小有硬性限制”的来源,请提供一下吗? - Danny Rancher
你的链接已经明确地说了两次。 - user207421

-1

有一些传言证据表明,在某些Windows版本上尝试一次性传输超过64MB的数据会导致复制速度变慢。因此需要进行检查:这似乎是在Windows上实现transferTo操作的底层本地代码的某些细节导致的结果。


有什么具体的证据吗?是哪个Windows版本?毫无根据的传言并不是一个答案。 - user207421
啊,证据。看一下http://bugs.sun.com/view_bug.do?bug_id=6822107:注意引用的64MB数字。我猜那是这个特定魔数最干净的来源。 - Femi
我几年前读过它们。在那个错误报告中,或者在与之相关的两个错误报告中,都没有提到“特定的Windows版本”或“导致复制变慢”的内容。实际评估中的数字是1.5GB。 - user207421

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接