Объединить огромные файлы без загрузки всего файла в память?

Я хочу объединить огромные файлы, содержащие строки, в один файл и пытался использовать nio2. Я не хочу загружать весь файл в память, поэтому попробовал с BufferedReader:

public void mergeFiles(filesToBeMerged) throws IOException{

Path mergedFile = Paths.get("mergedFile");
Files.createFile(mergedFile);

List<Path> _filesToBeMerged = filesToBeMerged;

try (BufferedWriter writer = Files.newBufferedWriter(mergedFile,StandardOpenOption.APPEND)) {
        for (Path file : _filesToBeMerged) {
// this does not work as write()-method does not accept a BufferedReader
            writer.append(Files.newBufferedReader(file));
        }
    } catch (IOException e) {
        System.err.println(e);
    }

}

Я попробовал это с этим, это работает, но формат строк (например, новые строки и т. д. не копируются в объединенный файл):

...
try (BufferedWriter writer = Files.newBufferedWriter(mergedFile,StandardOpenOption.APPEND)) {
        for (Path file : _filesToBeMerged) {
//              writer.write(Files.newBufferedReader(file));
            String line = null;


BufferedReader reader = Files.newBufferedReader(file);
            while ((line = reader.readLine()) != null) {
                    writer.append(line);
                    writer.append(System.lineSeparator());
             }
reader.close();
        }
    } catch (IOException e) {
        System.err.println(e);
    }
...

Как я могу объединить огромные файлы с NIO2, не загружая весь файл в память?


person nimo23    schedule 28.08.2014    source источник


Ответы (3)


Если вы хотите эффективно объединить два или более файлов, вы должны спросить себя, с какой стати вы используете char на основе Reader и Writer для выполнения этой задачи.

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

И, кстати, BufferedReader и BufferedWriter отнюдь не NIO2 артефакты. Эти классы существуют с самой первой версии Java.

При использовании побайтового копирования через настоящие функции NIO файлы могут передаваться без участия Java-приложения, в лучшем случае передача будет выполняться непосредственно в буфере файловой системы:

import static java.nio.file.StandardOpenOption.*;

import java.io.IOException;
import java.nio.channels.FileChannel;
import java.nio.file.Path;
import java.nio.file.Paths;

public class MergeFiles
{
  public static void main(String[] arg) throws IOException {
    if(arg.length<2) {
      System.err.println("Syntax: infiles... outfile");
      System.exit(1);
    }
    Path outFile=Paths.get(arg[arg.length-1]);
    System.out.println("TO "+outFile);
    try(FileChannel out=FileChannel.open(outFile, CREATE, WRITE)) {
      for(int ix=0, n=arg.length-1; ix<n; ix++) {
        Path inFile=Paths.get(arg[ix]);
        System.out.println(inFile+"...");
        try(FileChannel in=FileChannel.open(inFile, READ)) {
          for(long p=0, l=in.size(); p<l; )
            p+=in.transferTo(p, l-p, out);
        }
      }
    }
    System.out.println("DONE.");
  }
}
person Holger    schedule 28.08.2014
comment
Вау, это решение действительно отличное, а исходный код такой короткий. Спасибо! Знаете ли вы решение на основе nio2 для РАЗДЕЛЕНИЯ БОЛЬШОГО ФАЙЛА на набор файлов меньшего размера? На самом деле, я использую что-то вроде этого Todayguesswhat.blogspot.de/2014/05/. - person nimo23; 28.08.2014
comment
@nimo23: ну, я думаю, когда пытаешься понять код моего ответа, особенно то, что FileChannel.transferTo, вы поймете, как может выглядеть решение для разделения (читай: очень аналогичный). Если у вас возникли трудности с его реализацией, вы можете открыть новый вопрос. - person Holger; 28.08.2014
comment
Хорошо, я попробую сам и предоставлю решение здесь! - person nimo23; 28.08.2014
comment
Хорошо, я опубликовал решение: stackoverflow.com/questions/25553673/. Я не могу найти решение с nio2, так как с nio2 размер разделенных файлов можно уменьшить только на размер файла. Однако я хочу разбить текстовые файлы по номерам строк. Находите ли вы (лучшее) решение для метода splitTextFiles() с помощью nio2? - person nimo23; 28.08.2014

С участием

Files.newBufferedReader(file).readLine()

вы каждый раз создаете новый буфер, и он всегда сбрасывается в первой строке.

Заменить

BufferedReader reader = Files.newBufferedReader(file);
while ((line = reader.readLine()) != null) {
  writer.write(line);
}

и .close() читателю, когда закончите.

person Marco Acierno    schedule 28.08.2014
comment
спасибо, я внес изменения в исходный код. Знаете ли вы, как я могу сохранить формат объединенных файлов в mergedFile-File? Например, в объединенных файлах есть символы возврата каретки или пустые строки. При использовании описанного выше метода все это не копируется в файл mergedFile. - person nimo23; 28.08.2014
comment
Не уверен, что вы имеете в виду, но вы можете вручную добавить новую строку, используя write.write(System.lineSeparator()); - person Marco Acierno; 28.08.2014
comment
Мне интересно, что более производительно. Приведенное выше решение или решение на programcreek.com/2012/09/ слияние файлов в Java. Знаете, какой из них более производительный? - person nimo23; 28.08.2014
comment
@nimo23 напиши для него тест. У вас есть большой файл, поэтому выполните копирование пару раз и проверьте, сколько времени занял один метод и сколько времени — другой. - person Michal Gruca; 29.08.2014

readLine() не дает окончания строки ("\n" или "\r\n"). Это была ошибка.

while ((line = reader.readLine()) != null) {
    writer.write(line);
    writer.write("\r\n"); // Windows
}

Вы также можете игнорировать эту фильтрацию (возможно, разных) окончаний строк и использовать

try (OutputStream out = new FileOutputStream(file);
    for (Path source : filesToBeMerged) {
        Files.copy(path, out);
        out.write("\r\n".getBytes(StandardCharsets.US_ASCII));
    }
}

Это явно записывает новую строку в случае, если последняя строка не заканчивается разрывом строки.

Все еще может быть проблема с необязательным уродливым символом Unicode BOM, чтобы пометить текст как UTF-8/UTF-16LE/UTF-16BE в начале файла.

person Joop Eggen    schedule 28.08.2014