Скопируйте большие файлы максимально быстро

1

Я попытался найти способ скопировать большие файлы самым быстрым способом...

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

}

Я протестировал его, и результат был очень впечатляющим... но есть большая проблема, скопированный файл очень большой, чем текущий файл !!!

поэтому, пожалуйста, проверьте его и помогите мне найти проблему, спасибо :))

Теги:
multithreading
filechannel

2 ответа

2
Лучший ответ

Начиная с каждого цикла вы увеличиваете позицию по счету + 1, и вы делаете Transfer with '(fis, fos, position, position + count), ваш код будет создавать объекты 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() чтобы перейти в нужное место. Мне непонятно, какой хаос может произойти, поскольку несколько потоков расширяют размер файла таким образом.


Это прекрасно, чтобы экспериментировать, и я рекомендую вам попробовать это и сравнить. Однако комментарии верны, что подход ошибочен. Существует только один диск, поддерживаемый только одним буфером файловой системы, и борьба с несколькими потоками не заставит его работать быстрее - и может замедлить работу.

Вы вряд ли улучшаетесь:

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

Также обратите внимание, что очень сложно судить о производительности файловых операций, поскольку файловая система будет кэшировать как чтение, так и запись, поэтому очень многое из того, что вы делаете, будет просто супер-дешевыми операциями в ОЗУ.

Также обратите внимание, что, по крайней мере, при бенчмаркинге вам понадобится join() со всеми потоками, которые вы начали, прежде чем рассматривать завершение копирования.

  • 0
    большое спасибо :)
5

Это проблема 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 уже здесь довольно долгое время.

  • 1
    На машине любого уважающего себя разработчика эта программа кеширует файл в памяти, а ОС запишет грязный кеш ФС после завершения программы. Добавьте синхронизацию времени после выполнения программы и добавьте время.
  • 0
    @fge Спасибо :)

Ещё вопросы

Сообщество Overcoder
Наверх
Меню