【问题标题】:Copy large files in fastest way possible以最快的方式复制大文件
【发布时间】:2014-04-25 13:21:14
【问题描述】:

我试图找到一种以最快的方式复制大文件的方法...

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;

public class FastFileCopy {


public static void main(String[] args) {
    try {
        String from = "...";
        String to = "...";
        FileInputStream fis = new FileInputStream(from);
        FileOutputStream fos = new FileOutputStream(to);
        ArrayList<Transfer> transfers = new ArrayList<>();
        long position = 0, estimate;
        int count = 1024 * 64;
        boolean lastChunk = false;
        while (true) {
            if (position + count < fis.getChannel().size()) {
                transfers.add(new Transfer(fis, fos, position, position + count));
                position += count + 1;
                estimate = position + count;
                if (estimate >= fis.getChannel().size()) {
                    lastChunk = true;
                }
            } else {
                lastChunk = true;
            }
            if (lastChunk) {
                transfers.add(new Transfer(fis, fos, position, fis.getChannel().size()));
                break;
            }
        }
        for (Transfer transfer : transfers) {
            transfer.start();
        }
    } catch (IOException ex) {
        ex.printStackTrace();
    }
}

}

然后创建这个类:

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;

public class Transfer extends Thread {

private FileChannel inChannel = null;
private FileChannel outChannel = null;
private long position, count;

public Transfer(FileInputStream fis, FileOutputStream fos, long position, long count) {
    this.position = position;
    this.count = count;
    inChannel = fis.getChannel();
    outChannel = fos.getChannel();
}

@Override
public void run() {
    try {
        inChannel.transferTo(position, count, outChannel);
    } catch (IOException e) {
        e.printStackTrace();
    }
}

}

我测试了它,结果非常令人印象深刻...... 但是有个大问题,复制出来的文件比现在的文件大很多!!!

所以,请检查并帮助我找到问题,谢谢:))

【问题讨论】:

  • Files.copy(source, destination) 对您来说不够快吗?此外,如果文件位于单个硬盘驱动器上,则使用多个线程将降低性能。
  • no :)) ...您可以通过这种方式在 20 秒内复制 3 GB
  • 您是否至少尝试过 Files.copy()
  • 除了代码中的错误,究竟是什么让您认为多线程会使其更快?你有多线程磁盘吗?也许是条纹?
  • 线程不可能加速一个可能占用 1% CPU 的操作。

标签: java multithreading file-copying filechannel


【解决方案1】:

这是一个 XY 问题。只需使用Files.copy()

看一下,看看这对你来说是否不够快:

$ ls -lh ~/ubuntu-13.04-desktop-amd64.iso 
-rw-rw-r-- 1 fge fge 785M Jul 12  2013 /home/fge/ubuntu-13.04-desktop-amd64.iso
$ cat Foo.java 
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;

public class Foo
{
    public static void main(final String... args)
        throws IOException
    {
        Files.copy(Paths.get("/home/fge/ubuntu-13.04-desktop-amd64.iso"),
            Paths.get("/tmp/t.iso"), StandardCopyOption.REPLACE_EXISTING);
    }
}
$ time java Foo

real    0m1.860s
user    0m0.077s
sys 0m0.648s
$ time java Foo

real    0m1.851s
user    0m0.101s
sys 0m0.598s

而且它可能会更快。天知道为什么,Oracle 不使用 sendfile(2),尽管这是 Java 8 并且 Linux 2.2 已经出现了很长时间。

【讨论】:

  • 在任何自尊的开发者的机器上,这个程序都会将文件缓存在内存中,而操作系统将在程序结束之后写回脏的FS缓存。在程序执行后添加时间同步并添加时间。
【解决方案2】:

由于每个循环都会将 position 增加 count+1,并且使用 `(fis,fos,position,position+count) 进行 Transfer,因此您的代码将按如下方式创建 Transfer 对象:

new Transfer(fis, fos, 0,count)
new Transfer(fis, fos, count+1, 2count+1)
new Transfer(fis, fos, 2count+2, 3count+2)
new Transfer(fis, fos, 3count+3, 4count+3)
...

因此,尽管您将创建 filesize / count 传输类,但您要求传输 (count + 1) * (1 + 2 + 3 + ...) 字节的总数。

此外,我认为FileChannel.TransferTo() 的工作方式与您认为的不同。 position 指定源文件中您开始阅读的位置。它没有指定您在目标通道中写入的位置。所以即使你得到正确的大小,你最终也会得到正确大小的输出文件,但内容会按照线程碰巧写入它们的顺序混乱。

您可以拨打outChannel.position() 跳转到正确的位置。我不清楚当多个线程以这种方式扩展文件大小时会发生什么样的混乱。


进行实验很好,我鼓励您尝试一下并进行基准测试。然而,cmets 认为该方法被误导是正确的。只有一个磁盘,仅由一个文件系统缓冲区支持,并且让多个线程争夺它不会使其工作更快 - 并且可能会使其更慢。

你不太可能改进:

 long count = 0;
 long size = src.size();
 while(count < size) {
    count += src.transferTo(count, size - count, dest);
 }

还要注意,很难对文件操作的性能做出判断,因为文件系统会同时缓存读取和写入,所以你所做的很多事情都只是对 RAM 的超廉价操作。

另外请注意,至少在进行基准测试时,您需要 join() 处理已启动的所有线程,然后才能考虑复制完成。

【讨论】:

    猜你喜欢
    • 2018-09-07
    • 1970-01-01
    • 1970-01-01
    • 2021-07-24
    • 1970-01-01
    • 2017-01-19
    • 2012-07-02
    • 1970-01-01
    相关资源
    最近更新 更多