在Java中复制和移动文件,不同方法的解释和比较

Copy and move files in Java, explanation and comparison of different approaches

我实现了一个文件操作功能,我注意到Java提供了多种复制和移动文件的技术。您可以在下面找到代码片段,简要描述了这些方法:

方法 #1:

File from = new File(src.getPath());
File to = new File(dst.getPath());

from.renameTo(to);

方法 #2:

FileChannel inChannel = new FileInputStream(src).getChannel();
FileChannel outChannel = new FileOutputStream(dst).getChannel();

inChannel.transferTo(0, inChannel.size(), outChannel);

方法 #3:

InputStream in = getContentResolver().openInputStream(selectedImageUri);
OutputStream out = new FileOutputStream("/sdcard/wallpapers/" + wall);

byte[] buffer = new byte[1024];
int read;

while ((read = in.read(buffer)) != -1) {
    out.write(buffer, 0, read);
}

方法 #4:

import static java.nio.file.StandardCopyOption.*;

Files.copy(source, target, REPLACE_EXISTING);

所有这些方法都有效,但我不知道什么时候应该使用它们?每种方法的优缺点是什么,尤其是从性能和可靠性的角度来看?是否存在任何特定情况下我必须更喜欢一种技术而不是另一种技术?

已经讨论够了here and the following is from here

您的第一种方法是文件重命名,与文件复制无关

java.io.File class 没有任何将文件从源复制到目标的快捷方式。

1. Using Stream: 这是java中文件复制的常规方式,这里我们创建两个Files,source和destination。然后我们从源创建 InputStream 并使用 OutputStream 将其写入目标文件。

2。使用 java.nio.channels.FileChannel: Java NIO classes 是在 Java 1.4 中引入的,FileChannel 可用于在 java 中复制文件。根据 transferFrom() 方法 javadoc,这种复制文件的方式应该比使用 Streams 复制文件更快。

3。使用 Apache Commons IO: Apache Commons IO FileUtils.copyFile(File srcFile, File destFile) 可用于复制 java 中的文件。如果您已经在您的项目中使用 Apache Commons IO,那么使用它来简化代码是有意义的。它在内部使用 Java NIO FileChannel,因此如果您尚未将其用于其他功能,则可以避免使用此包装方法。

4. Java 7 Files class: 如果你正在处理 Java 7,你可以使用 Files class copy() 方法将文件复制到 [=41] =].它使用文件系统提供程序来复制文件。

现在要看看这些方法中哪一种更有效,我们将在一个简单的程序中使用它们中的每一种来复制一个大文件[1 GB]。为了避免缓存带来的任何性能加速,我们将使用四个不同的源文件和四个不同的目标文件。{参考 link 中的代码}

Time taken by FileStreams Copy = 127572360
Time taken by FileChannels Copy = 10449963
Time taken by Java7 Files Copy = 10808333
Time taken by Apache Commons IO Copy = 17971677

从输出中可以清楚地看出,流式复制是在 Java 中复制文件的最佳方式。 FileChannels 是复制大文件的最佳方式。如果您处理更大的文件,您会发现速度差异更大

我们可以把你的四种做法分为两种:

  1. 使用内置的标准库方法(例如File.renameTo()Files.move())。
  2. 我们自己完成工作 - 通过将字节从源复制到目标。

首先,请注意 File 没有复制方法,因此当您谈论复制时,您只有一个内置的标准库方法选项。

另请注意 "do the work ourselves" 重命名将非常糟糕 - 您将复制整个文件,然后删除旧文件。这不是一个好的或有效的方法。在大多数情况下,renaming/moving 在同一个文件系统中只需要更改文件元数据而无需实际触及内容,因此使用标准库确实要好得多。

所以你有两种情况:

重命名

这些选项实际上使用了 File.renameTo()Files.move()。使用流和复制数据毫无意义。

File 是一个过时的 class。它真的不应该再被使用了。有一个很好的 explanation why,它总结了这样一个事实,即 File 在其任何标准方法失败时不会为您提供任何信息,而 Files 在以下情况下为您提供非常准确的异常发生了。

正在复制

您有两个选择 - 使用 Files.copy() 或 "do it yourself" 方法之一。

到目前为止,如果您复制的是实际文件,您的选择应该是Files.copy()。无需重新发明轮子。它完全按照您的意愿行事,有据可查,您不太可能不小心引入错误。是的,它非常有效。

Files.copy() 依赖底层 "providers" 进行操作。这意味着有专门的供应商(或操作系统)特定的 classes 执行对该文件系统最有效的操作。无论是 Linux 文件系统还是 Windows 文件系统,副本都会针对它进行优化。甚至还有针对特殊情况的提供程序,例如 zip 文件,因此您可以使用 Files.copy() 复制 zip、jar 或 war 文件中的文件——如果您尝试 [=82,这会复杂得多=]方法。

此外,Files.copy() 检查了很多您在编写 "your own" 副本时可能会忘记的内容。例如,您是否记得检查您正在读取的文件和您正在写入的文件不是同一个文件?这可能会造成严重的麻烦。 Files.copy() 做到了。它检查权限,检查副本的目标是否是一个目录,等等。所以还是很靠谱的。

那么为什么您可以选择 "your own"?因为好吧,Java 是一种通用语言。您可以选择从文件读取,也可以选择写入文件,因此您 可以 编写自己的 "copy" 方法。那并不意味着你应该。

请注意,在您的 "approach #3" 中,"source" 文件实际上并不是一个文件!它是从图像 URI 生成的,这意味着它可以是网络源。当您的源不是文件,而是基于套接字、数据库 BLOB、Web 服务器请求等的流或通道时,您不能真正使用 Files.copy()。这是您需要自己编写的地方。

实际上,Files 也有从文件复制到 OutputStream 或从 InputStream 复制到文件的选项,所以如果复制的一侧是流并且另一个文件,你可以使用它。它将是可读的、安全的,并且会抛出有意义的异常。

所以写你自己的副本:

  • 当您需要将数据从源移动到不是文件的目标时,
  • 当您需要以某种方式过滤或处理数据而不是按原样从源复制到目标时,
  • 当您使用 1.7 之前的旧版本 Java 时。在这种情况下,频道可能比流更好。