传统 IO 复制

这是 Java 里最常见的文件拷贝方式,效率比不上其它两种,而且在我本机上拷贝大文件会出现失败的情况。
拷贝小文件时,与其它两种方式差别不大,文件越大,差距越明显。

  1. public static void traditionalCopy(File source, File desc) {
  2. try (final FileInputStream inputStream = new FileInputStream(source);
  3. final FileOutputStream outputStream = new FileOutputStream(desc)) {
  4. byte[] length = new byte[2097152];
  5. int read;
  6. final long start = System.currentTimeMillis();
  7. while ((read = inputStream.read(length)) != -1) {
  8. outputStream.write(read);
  9. }
  10. System.out.println("traditionalCopy耗时:" + (System.currentTimeMillis() - start));
  11. } catch (Exception e) {
  12. e.printStackTrace();
  13. throw new RuntimeException();
  14. }
  15. }

通过通道拷贝文件

我们有时需要将一个文件中的内容复制到另一个文件中去,最容易想到的做法是利用传统的 IO 将源文件中的内容读取到内存中,然后再往目标文件中写入。
现在,有了 NIO,我们可以利用更方便快捷的方式去完成复制操作。FileChannel 提供了一对数据转移方法 - transferFrom/transferTo,通过使用这两个方法,即可简化文件复制操作。
在拷贝大文件的时候,通过通道拷贝文件的效率是最高的(在我本机上测试出的结果)。

  1. public static void copyFileByChannel(File source, File dest) {
  2. try (FileChannel sourceChannel = new FileInputStream(source).getChannel();
  3. FileChannel targetChannel = new FileOutputStream(dest).getChannel()) {
  4. int length = 2097152;
  5. final long start = System.currentTimeMillis();
  6. while (sourceChannel.position() != sourceChannel.size()) {
  7. if (sourceChannel.size() - sourceChannel.position() < length) {
  8. length = (int) (sourceChannel.size() - sourceChannel.position());
  9. }
  10. sourceChannel.transferTo(sourceChannel.position(), length, targetChannel);
  11. sourceChannel.position(sourceChannel.position() + length);
  12. }
  13. System.out.println("copyFileByChannel耗时:" + (System.currentTimeMillis() - start));
  14. } catch (Exception e) {
  15. e.printStackTrace();
  16. throw new RuntimeException();
  17. }
  18. }

通过内存映射拷贝文件

内存映射这个概念源自操作系统,是指将一个文件映射到某一段虚拟内存(物理内存可能不连续)上去。我们通过对这段虚拟内存的读写即可达到对文件的读写的效果,从而可以简化对文件的操作。
Unix/Linux 操作系统内存映射的系统调用 mmap,Java 在这个系统调用的基础上,封装了 Java 的内存映射方法。
拷贝小文件时,内存映射的方式会比通道拷贝的方式更有效率,但是差距不是很明显。

  1. public static void copyByMap(File source, File dest) {
  2. try (FileChannel sourceChannel = new FileInputStream(source).getChannel();
  3. FileChannel targetChannel = new FileOutputStream(dest).getChannel()) {
  4. int length = 2097152;
  5. final long start = System.currentTimeMillis();
  6. while (sourceChannel.position() != sourceChannel.size()) {
  7. if (sourceChannel.size() - sourceChannel.position() < length) {
  8. length = (int) (sourceChannel.size() - sourceChannel.position());
  9. }
  10. MappedByteBuffer map = sourceChannel.map(FileChannel.MapMode.READ_ONLY, sourceChannel.position(), length);
  11. targetChannel.write(map);
  12. sourceChannel.position(sourceChannel.position() + length);
  13. }
  14. System.out.println("copyByMap耗时:" + (System.currentTimeMillis() - start));
  15. } catch (Exception e) {
  16. e.printStackTrace();
  17. throw new RuntimeException();
  18. }
  19. }