Як скопіювати файли великих даних по черзі?


9

У мене CSVфайл 35 Гб . Я хочу прочитати кожен рядок і записати його в новий CSV, якщо він відповідає умові.

try (BufferedWriter writer = Files.newBufferedWriter(Paths.get("source.csv"))) {
    try (BufferedReader br = Files.newBufferedReader(Paths.get("target.csv"))) {
        br.lines().parallel()
            .filter(line -> StringUtils.isNotBlank(line)) //bit more complex in real world
            .forEach(line -> {
                writer.write(line + "\n");
        });
    }
}

Це займає ок. 7 хвилин. Чи можна ще більше прискорити цей процес?


1
Так, ви можете спробувати не робити цього з Java, а скоріше зробити це безпосередньо зі свого Linux / Windows / тощо. операційна система. Інтерпретована Java, і завжди у неї буде накладні витрати. Окрім цього, ні, я не маю очевидного способу пришвидшити це, і 7 хвилин для 35 Гб здаються мені розумними.
Тім Бігелейзен

1
Можливо, видалення parallelробить це швидше? І це не перетасовує лінії навколо?
Тіло

1
Створіть BufferedWriterсебе, використовуючи конструктор, який дозволяє встановити розмір буфера. Можливо, більший (або менший) розмір буфера призведе до зміни. Я б спробував співставити BufferedWriterрозмір буфера з розміром буфера хост-операційної системи.
Авра

5
@TimBiegeleisen: "Java інтерпретується" в кращому випадку вводить в оману, а також майже завжди помиляється. Так, для деяких оптимізацій вам може знадобитися покинути світ JVM, але зробити це швидше на Java, безумовно, можливо.
Йоахім Зауер

1
Ви повинні профайлювати додаток, щоб побачити, чи є гарячі точки, з якими ви можете щось зробити. Ви не зможете зробити багато з приводу сирого IO (буфер 8192 байт за замовчуванням не так вже й погано, оскільки тут задіяні розміри секторів тощо), але можуть статися речі (внутрішньо), які, можливо, зможете працювати з.
Каяман

Відповіді:


4

Якщо це варіант, ви можете використовувати GZipInputStream / GZipOutputStream для мінімізації вводу / виводу диска.

Files.newBufferedReader / Writer використовують розмір буфера за замовчуванням, 8 КБ, я вважаю. Ви можете спробувати більший буфер.

Перетворення на String, Unicode, сповільнюється до (і використовує двічі пам'ять). Використовуваний UTF-8 не такий простий, як StandardCharsets.ISO_8859_1.

Найкраще, якщо ви можете працювати з байтами здебільшого і лише для конкретних полів CSV перетворюйте їх у String.

Файл, відображений на пам'яті, може бути найбільш підходящим. Паралелізм може використовуватися діапазонами файлів, випилюючи файл.

try (FileChannel sourceChannel = new RandomAccessFile("source.csv","r").getChannel(); ...
MappedByteBuffer buf = sourceChannel.map(...);

Це стане трохи більшим кодом, що набирає рядки прямо (byte)'\n', але не надто складно.


Проблема з читанням байтів полягає в тому, що в реальному світі я повинен оцінювати початок рядка, підстроковуючи за певним символом і записувати лише решту частину рядка у вихідний файл. Тож я, певно, не можу читати рядки лише у байтах?
membersound

Я щойно перевірив GZipInputStream + GZipOutputStreamцілком запам’ятовування на рамковому диску. Продуктивність була набагато гірше ...
membersound

1
На Gzip: тоді це не повільний диск. Так, байти - це варіант: нові рядки, кома, вкладка, крапка з комою все можна обробляти як байти, і вони будуть значно швидшими, ніж як String. Байти як UTF-8 до UTF-16 char до String до UTF-8 до байтів.
Joop Eggen

1
Просто з часом картографуйте різні частини файлу. Коли ви досягнете межі, просто створіть нову MappedByteBufferз останньої відомої-хорошої позиції ( FileChannel.mapзаймає довго).
Іоахім Зауер

1
У 2019 році використовувати не потрібно new RandomAccessFile(…).getChannel(). Просто використовуйте FileChannel.open(…).
Холгер

0

ви можете спробувати це:

try (BufferedWriter writer = new BufferedWriter(new FileWriter(targetFile), 1024 * 1024 * 64)) {
  try (BufferedReader br = new BufferedReader(new FileReader(sourceFile), 1024 * 1024 * 64)) {

Думаю, це заощадить вам одну-дві хвилини. випробування можна зробити на моїй машині приблизно за 4 хвилини, вказавши розмір буфера.

чи могло бути швидше? спробуйте це:

final char[] cbuf = new char[1024 * 1024 * 128];

try (Writer writer = new FileWriter(targetFile)) {
  try (Reader br = new FileReader(sourceFile)) {
    int cnt = 0;
    while ((cnt = br.read(cbuf)) > 0) {
      // add your code to process/split the buffer into lines.
      writer.write(cbuf, 0, cnt);
    }
  }
}

Це повинно економити три-чотири хвилини.

Якщо цього ще недостатньо. (Я вважаю, що ви ставите питання, ймовірно, вам потрібно виконати завдання кілька разів). якщо ви хочете зробити це за одну хвилину чи навіть пару секунд. то вам слід обробити дані та зберегти їх у db, а потім обробити завдання на декількох серверах.


На ваш останній приклад: як я можу потім оцінювати cbufвміст і виписувати лише частини? І мені доведеться скинути буфер один раз у повному обсязі? (Як я можу знати , що буфер заповнений?)
membersound

0

Завдяки всім вашим пропозиціям, найшвидше, що я придумав, - це обмін письменником BufferedOutputStream, що дало приблизно 25% покращення:

   try (BufferedReader reader = Files.newBufferedReader(Paths.get("sample.csv"))) {
        try (BufferedOutputStream writer = new BufferedOutputStream(Files.newOutputStream(Paths.get("target.csv")), 1024 * 16)) {
            reader.lines().parallel()
                    .filter(line -> StringUtils.isNotBlank(line)) //bit more complex in real world
                    .forEach(line -> {
                        writer.write((line + "\n").getBytes());
                    });
        }
    }

І все-таки результати BufferedReaderкраще, ніж BufferedInputStreamу моєму випадку.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.