Записывайте большие файлы, используя класс RandomAccessFile

Мне нужно скопировать большие файлы (ГБ) в другой файл (контейнер), и мне было интересно узнать о производительности и использовании оперативной памяти.

Чтение всего исходного файла следующим образом:

RandomAccessFile f = new RandomAccessFile(origin, "r");
originalBytes = new byte[(int) f.length()];
f.readFully(originalBytes);

А позже скопируйте все в контейнер следующим образом:

RandomAccessFile f2 = new RandomAccessFile(dest, "wr");
f2.seek(offset);
f2.write(originalBytes, 0, (int) originalBytes.length);

все в памяти, верно? Таким образом, копирование больших файлов может повлиять на память и привести к исключению OutOfMemory?

Лучше ли читать байты исходного файла по байтам, а не целиком? В таком случае, как мне поступить? Заранее спасибо.

ИЗМЕНИТЬ:

Следуя ответу mehdi maick, я наконец нашел решение: я могу использовать RandomAccessFile в качестве пункта назначения, как я хотел, и поскольку RandomAccessFile имеет метод "getChannel", который возвращает FileChannel, я могу передать его следующему методу, который сделает копию (32 КБ за раз) файла в нужном мне месте назначения:

     public static void copyFile(File sourceFile, FileChannel destination, int position) throws IOException {
            FileChannel source = null;
            try {
                source = new FileInputStream(sourceFile).getChannel();
                destination.position(position);
                int currentPosition=0;
                while (currentPosition < sourceFile.length())
                    currentPosition += source.transferTo(currentPosition, 32768, destination);
            } finally {
                if (source != null) {
                    source.close();
                }

            }
        }

person navy1978    schedule 31.01.2019    source источник
comment
Почему бы вам не использовать вместо этого байтовый буфер и не читать исходный файл по частям? С точки зрения производительности это здорово.   -  person Alexander Petrov    schedule 01.02.2019
comment
Читать блоками/фрагментами, например. 64 КБ за раз, используя FileInputStream и FileOutputStream   -  person Andreas    schedule 01.02.2019
comment
@AlexandarPetrov, не могли бы вы привести пример, учитывая, что файл назначения должен быть записан с помощью RandomAccessFile? Спасибо.   -  person navy1978    schedule 01.02.2019
comment
@Андреас, то же самое для тебя;)   -  person navy1978    schedule 01.02.2019
comment
Почему конечный файл должен быть записан с RandomAccessFile? Разве вы не просто объединяете существующие файлы в комбинированный файл?   -  person Andreas    schedule 01.02.2019
comment
@Andreas Поскольку контейнер содержит заголовок и объединение разных файлов, поэтому мне нужно искать в правильной позиции и писать оттуда ...   -  person navy1978    schedule 01.02.2019
comment
Конкатенация на самом деле не является конкатенацией (один файл за другим), заголовок (контейнера) содержит смещение, с которого начинается один файл....   -  person navy1978    schedule 01.02.2019


Ответы (2)


Попробуйте использовать async nio Channel


    public void copyFile(String src, String target) {
        final String fileName = getFileName(src);
        try (FileChannel from = (FileChannel.open(Paths.get(src), StandardOpenOption.READ));
                FileChannel to = (FileChannel.open(Paths.get(target + "/" + fileName), StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE))) {
            transfer(from, to, 0l, from.size());
        }
    }

    private String getFileName(final String src) {
        File file = new File(src);
        if (file.isFile()) {
            return file.getName();
        } else {
            throw new RuntimeException("src is not a valid file");
        }
    }

    private void transfer(final FileChannel from, final FileChannel to, long position, long size) throws IOException {
        while (position < size) {
            position += from.transferTo(position, Constants.TRANSFER_MAX_SIZE, to);
        }
    }

Это создаст асинхронные каналы чтения и записи и эффективно перенесет данные от первого к более позднему.

person mehdi maick    schedule 31.01.2019
comment
Контейнер (файл назначения) содержит конкатенацию файлов, мне нужно искать правильное смещение в контейнере и начинать писать оттуда. Вот почему я задал вопрос о примере, в котором мы используем RandomAccessFile для записи в место назначения (контейнер)... Не могли бы вы адаптировать свой пример с использованием RandomAccessFile для места назначения? - person navy1978; 01.02.2019
comment
FileChannel предоставляет метод position(long position) для поиска точного требуемого положения. - person mehdi maick; 01.02.2019
comment
Я не видел этого раньше .. Я проголосую за вас, потому что я не могу проверить это сейчас, если все в порядке, я приму ваш ответ ... Спасибо на данный момент;) - person navy1978; 01.02.2019
comment
Спасибо, я принял ваш ответ и редактирую свой вопрос с найденным решением... ;) - person navy1978; 02.02.2019
comment
Рад, что смог помочь ;) - person mehdi maick; 02.02.2019

Читать блоками/фрагментами, например. 64 КБ за раз, используя FileInputStream и FileOutputStream.

Если вам нужно повысить производительность, вы можете попробовать использовать потоки, один поток для чтения и другой поток для записи.

Вы также можете повысить производительность, используя прямые буферы NIO.
См., например. Простое правило, когда мне следует использовать прямые буферы с Java NIO для сетевого ввода-вывода?

person Andreas    schedule 31.01.2019
comment
что произойдет с последним фрагментом размером 64 КБ, если он содержит только 32 КБ? он запишет все 64к (последние 32к с нулями)? - person navy1978; 01.02.2019
comment
@ navy1978 Почему он записывает 64 КБ, если вы читаете в буфер только 32 КБ? Если это так, вы написали код неправильно. - person Andreas; 01.02.2019
comment
Я не понимаю вас, вы сказали читать 64 КБ за раз, а не я ... Я спрашиваю вас, что происходит с последним куском, если он содержит только 32 КБ (файл действительно может быть не кратным 64 КБ) это делает смысл для вас? - person navy1978; 01.02.2019
comment
@ navy1978 У вас есть буфер, например, размером 64 КБ, вы просите прочитать байты из входного файла в буфер, затем вы переключаетесь и записываете фактическое количество прочитанных байтов в выходной файл. С какой стати вы думаете, что код, записывающий байты, не знает, заполнен ли буфер? - person Andreas; 01.02.2019
comment
Я спрашиваю, потому что я не знаю, как это работает, в RandomAccessFile метод «запись» (один из методов «запись») принимает массив байтов, поэтому мне было интересно, передавая массив 64 КБ в последний раз, это было бы напишите полное количество байтов или только заполненную часть... это было моим сомнением... - person navy1978; 01.02.2019
comment
@navy1978 Метод write(...) перегружен. Есть 3 его варианта. - person Andreas; 01.02.2019
comment
Ты читаешь меня? Прочитайте еще раз, вы обнаружите, что я написал один из способов «написать», вы хотите возразить без каких-либо причин или помочь? - person navy1978; 01.02.2019
comment
@navy1978 Ты читаешь меня? Существует ТРИ перегрузки метода. Перегрузка second принимает аргумент len, указывающий, сколько байтов из массива буферов нужно записать. Если буфер не заполнен, вы используете этот метод для записи только части буфера. Что из этого трудно понять? Вы смотрели на 3 метода, на которые я ссылался? Возможно, вы думали, что я упомянул и связал методы по какой-то причине? - person Andreas; 01.02.2019
comment
Я сейчас разговариваю по телефону и мне сложнее следить за тобой, может быть, это моя вина. Вы имеете в виду, что, используя последний фрагмент, я должен проверить, сколько байтов мне нужно использовать? Не проще ли привести пример? - person navy1978; 01.02.2019
comment
while ((len = read(buffer)) > 0) { write(buffer, 0, len); } --- Вы записываете количество байтов, на самом деле прочитанных, как Я уже сказал!!! - person Andreas; 01.02.2019