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

Я хочу объединить огромные файлы, содержащие строки, в один файл и попытался использовать 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 без загрузки всего файла в память?

4 ответа

Решение

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

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

И кстати, 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.");
  }
}

С

Files.newBufferedReader(file).readLine()

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

Заменить

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

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

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, чтобы пометить текст как UTF-8/UTF-16LE/UTF-16BE в начале файла.

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

  1. BufferedReader и BufferedWrtier
          private static void mergeFiles(List<Path> sources, Path destination) {
        try (BufferedWriter writer = new BufferedWriter(new FileWriter(destination.toFile(), true))) {
            for (Path path : sources) {
                try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(path.toFile())))) {
                    String line;
                    while ((line = reader.readLine()) != null) {
                        writer.write(line);
                        writer.newLine();
                    }
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
  1. Входной поток и файлы.копия
          private static void mergeFiles2(List<Path> sources, Path destination) {
        try {
            BinaryOperator<InputStream> sequenceInputStream = SequenceInputStream::new;
            List<InputStream> inputStreams = new ArrayList<>();

            for (Path path : sources) {
                InputStream is = Files.newInputStream(path, StandardOpenOption.READ);
                inputStreams.add(is);
            }

            InputStream streams = inputStreams.parallelStream().reduce(sequenceInputStream).orElseThrow(() -> new IllegalStateException("inputStreams reduce exception"));
            Files.copy(streams, destination, StandardCopyOption.REPLACE_EXISTING);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
  1. Файловый канал
          private static void mergeFiles3(List<Path> sources, Path destination) {
        try (FileChannel desChannel = FileChannel.open(destination, StandardOpenOption.WRITE, StandardOpenOption.CREATE)) {
            for (Path path : sources) {
                try (FileChannel srcChannel = FileChannel.open(path, StandardOpenOption.READ)) {
                    for (long position = 0, size = srcChannel.size(); position < size; ) {
                        position += srcChannel.transferTo(position, size - position, desChannel);
                    }
                }
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
Другие вопросы по тегам