Come copiare file di dati di grandi dimensioni riga per riga?


9

Ho un CSVfile da 35 GB . Voglio leggere ogni riga e scriverla in un nuovo CSV se corrisponde a una condizione.

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

Questo richiede circa. 7 minuti È possibile accelerare ulteriormente questo processo?


1
Sì, potresti provare a non farlo da Java, ma piuttosto farlo direttamente dal tuo Linux / Windows / ecc. sistema operativo. Java viene interpretato e ci sarà sempre un sovraccarico nell'usarlo. Oltre a questo, no, non ho alcun modo ovvio per accelerarlo e 7 minuti per 35 GB mi sembrano ragionevoli.
Tim Biegeleisen

1
Forse la rimozione lo parallelrende più veloce? E questo non mescola le linee intorno?
Thilo,

1
Crea BufferedWriterte stesso, usando il costruttore che ti consente di impostare la dimensione del buffer. Forse una dimensione del buffer più grande (o più piccola) farà la differenza. Vorrei provare ad abbinare la BufferedWriterdimensione del buffer alla dimensione del buffer del sistema operativo host.
Abra,

5
@TimBiegeleisen: "Java viene interpretato" è fuorviante nella migliore delle ipotesi e quasi sempre sbagliato. Sì, per alcune ottimizzazioni potrebbe essere necessario abbandonare il mondo JVM, ma farlo in modo più veloce in Java è sicuramente fattibile.
Joachim Sauer,

1
Dovresti profilare l'applicazione per vedere se ci sono hotspot su cui puoi fare qualcosa. Non sarai in grado di fare molto sull'IO grezzo (il buffer di 8192 byte predefinito non è poi così male, poiché ci sono dimensioni di settore ecc.), Ma potrebbero accadere cose (internamente) che potresti essere in grado di lavorare con.
Kayaman,

Risposte:


4

Se è un'opzione, puoi utilizzare GZipInputStream / GZipOutputStream per ridurre al minimo l'I / O del disco.

Files.newBufferedReader / Writer utilizza una dimensione del buffer predefinita, 8 KB, credo. Potresti provare un buffer più grande.

La conversione in String, Unicode, rallenta in (e utilizza il doppio della memoria). L'UTF-8 utilizzato non è così semplice come StandardCharsets.ISO_8859_1.

La cosa migliore sarebbe se puoi lavorare con i byte per la maggior parte e solo per campi CSV specifici convertirli in String.

Un file mappato in memoria potrebbe essere il più appropriato. Il parallelismo potrebbe essere utilizzato dagli intervalli di file, sputando il file.

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

Questo diventerà un po 'di codice, ottenendo le linee giuste (byte)'\n', ma non eccessivamente complesse.


Il problema con la lettura dei byte è che nel mondo reale devo valutare l'inizio della riga, sottostringendo su un carattere specifico e scrivere solo la parte rimanente della riga nel file di output. Quindi probabilmente non riesco a leggere le righe solo come byte?
membri del

Ho appena testato GZipInputStream + GZipOutputStreamcompletamente in memoria su un ramdisk. La performance è stata molto peggio ...
Membersound il

1
Su Gzip: quindi non è un disco lento. Sì, i byte sono un'opzione: newline, virgola, tabulazione, punto e virgola possono tutti essere gestiti come byte e saranno considerevolmente più veloci di come String. Byte da UTF-8 a UTF-16 char da String a UTF-8 a byte.
Joop Eggen,

1
Basta mappare diverse parti del file nel tempo. Quando raggiungi il limite, creane uno nuovo MappedByteBufferdall'ultima posizione nota valida ( FileChannel.mapimpiega molto tempo).
Joachim Sauer,

1
Nel 2019 non è necessario utilizzarlo new RandomAccessFile(…).getChannel(). Basta usare FileChannel.open(…).
Holger,

0

puoi provare questo:

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

Penso che ti farà risparmiare uno o due minuti. il test può essere eseguito sulla mia macchina in circa 4 minuti specificando la dimensione del buffer.

potrebbe essere più veloce? prova questo:

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

Questo dovrebbe farti risparmiare tre o quattro minuti.

Se non è ancora abbastanza. (Il motivo per cui immagino che tu faccia la domanda probabilmente è che devi eseguire l'attività ripetutamente). se vuoi farlo in un minuto o anche un paio di secondi. quindi è necessario elaborare i dati e salvarli in db, quindi elaborare l'attività da più server.


Per il tuo ultimo esempio: come posso quindi valutare il cbufcontenuto e scrivere solo parti? E dovrei ripristinare il buffer una volta pieno? (come posso sapere se il buffer è pieno?)
membersound,

0

Grazie a tutti i tuoi suggerimenti, il più veloce che mi è venuto in mente è stato di scambiare lo scrittore BufferedOutputStream, con un miglioramento di circa il 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());
                    });
        }
    }

Comunque si BufferedReadercomporta meglio rispetto BufferedInputStreamal mio caso.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.