2

大きなファイルを可能な限り高速にコピーする方法を見つけようとしました...

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();
    }
}

}

私はそれをテストし、結果は非常に印象的でした...しかし、大きな問題があります.コピーされたファイルは現在のファイルよりも非常に大きいです!!!

それで、それをチェックして、問題を見つけるのを手伝ってください、ありがとう:))

4

2 に答える 2

6

これは 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

そして、それはさらに速くなる可能性があります。sendfile(2)神はその理由を知っています。これがJava 8であり、Linux 2.2がかなり前から存在しているにもかかわらず、Oracleは使用しません。

于 2014-03-19T11:15:21.137 に答える
2

ループごとに位置を 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 / countTransfer クラスを作成しますが(count + 1) * (1 + 2 + 3 + ...)、合計でバイトを転送するよう求めています。

さらに、FileChannel.TransferTo()あなたが思うように機能するとは思いません。position読み取りを開始するソース ファイル内の位置を指定します。宛先チャネルで書き込む位置を指定するものではありません。そのため、サイズを正しく設定しても、最終的には適切なサイズの出力ファイルになりますが、内容はスレッドがたまたま書き込む順序でごちゃ混ぜになります。

outChannel.position()適切な場所にスキップするように呼び出すことができます。このように複数のスレッドがファイルサイズを拡張するときに、どのような混乱が発生する可能性があるかはわかりません。


実験しても問題ありません。これを試してベンチマークすることをお勧めします。ただし、アプローチが間違っているというコメントは正しいです。ディスクは 1 つだけで、1 つのファイルシステム バッファーに支えられているため、複数のスレッドがそれをめぐって競合しても、動作が速くなるわけではなく、遅くなる可能性があります。

あなたが改善する可能性は低いです:

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

ファイルシステムは読み取りと書き込みの両方をキャッシュするため、ファイル操作のパフォーマンスについて判断するのは非常に難しいことにも注意してください。

また、少なくともベンチマークを行うときはjoin()、コピーが完了したと見なす前に、開始したすべてのスレッドを使用する必要があることに注意してください。

于 2014-03-19T11:33:32.073 に答える