2010-03-01 43 views
50

Ho bisogno del consiglio di qualcuno che conosce molto bene Java e dei problemi di memoria. Ho un file di grandi dimensioni (qualcosa come 1.5 GB) e ho bisogno di tagliare questo file in molti (100 piccoli file per esempio) file più piccoli.Leggere file di grandi dimensioni in Java

So generalmente come farlo (utilizzando un BufferedReader), ma mi piacerebbe sapere se avete qualche consiglio in merito alla memoria, o suggerimenti su come farlo più veloce.

Il mio file contiene testo, non è binario e ho circa 20 caratteri per riga.

+7

Utilizzare API byte (ad esempio FileInputStream, ByteChannel), anziché API carattere (BufferedReader, etc.). Altrimenti, stai codificando e decodificando inutilmente. –

+3

La divisione di un file di testo utilizzando i byte sarebbe una cattiva idea. – james

risposta

25

Innanzitutto, se il file contiene dati binari, utilizzare BufferedReader sarebbe un grosso errore (perché si converte i dati in String, che non è necessario e potrebbe facilmente danneggiare i dati); dovresti usare invece un BufferedInputStream. Se si tratta di dati di testo e devi dividerli lungo interruzioni di riga, usare BufferedReader è OK (supponendo che il file contenga linee di una lunghezza ragionevole).

Per quanto riguarda la memoria, non ci dovrebbe essere alcun problema se si utilizza un buffer di dimensioni adeguate (userei almeno 1 MB per assicurarsi che l'HD sta facendo la lettura e la scrittura in gran parte sequenziale).

Se la velocità si rivela essere un problema, si potrebbe avere uno sguardo ai pacchetti java.nio - questi sono presumibilmente più veloce di java.io,

+0

Sì, userò BufferedReader perché ho un file di testo e ho bisogno di leggerlo riga per riga. Ora ho un altro problema: non riesco a rilevare la dimensione del nuovo file quando lo scrivo. L'idea è che quando la dimensione del nuovo file> xx MB genera un nuovo file. –

+1

@CC: potresti semplicemente continuare ad aggiungere la lunghezza della stringa delle linee che stai copiando. Ma dipende dal carattere che codifica come ciò si traduce in dimensione del file (e non funziona affatto con codifiche a lunghezza variabile come UTF-8) –

+0

suggerirei di aggiungere un FilterOutputStream personalizzato tra FileOutputStream (in basso) e OutputStreamWriter. Implementa questo filtro per tenere traccia del numero di byte che lo attraversano (Apache Commons I potrebbe già avere una tale utilità). – james

26

per risparmiare memoria, non conservare inutilmente/duplicare i dati in memoria (cioè non li si assegna a variabili fuori dal ciclo). Elabora l'output immediatamente non appena l'input arriva.

Non importa se stai utilizzando BufferedReader o meno. Non costerà molto più memoria, come suggeriscono implicitamente alcuni. Al massimo colpirà solo un po 'di% delle prestazioni. Lo stesso vale per l'utilizzo di NIO. Migliorerà solo la scalabilità, non l'uso della memoria. Diventerà interessante solo quando centinaia di thread sono in esecuzione sullo stesso file.

Basta scorrere il file, scrivere immediatamente ogni riga su un altro file mentre si legge, contare le righe e se raggiunge 100, quindi passare al file successivo, eccetera.

Kickoff esempio:

String encoding = "UTF-8"; 
int maxlines = 100; 
BufferedReader reader = null; 
BufferedWriter writer = null; 

try { 
    reader = new BufferedReader(new InputStreamReader(new FileInputStream("/bigfile.txt"), encoding)); 
    int count = 0; 
    for (String line; (line = reader.readLine()) != null;) { 
     if (count++ % maxlines == 0) { 
      close(writer); 
      writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("/smallfile" + (count/maxlines) + ".txt"), encoding)); 
     } 
     writer.write(line); 
     writer.newLine(); 
    } 
} finally { 
    close(writer); 
    close(reader); 
} 
+0

Sì, è sufficiente collegarlo da FileInputStream a FilOutputStream utilizzando solo un array di buffer di byte di dimensioni adeguate. –

+0

Non funziona per me contare le linee. Il fatto è: ho un file e ho bisogno di dividerlo in 200 (questo può cambiare, verrà dal database) i file per esempio. Come faccio? Il solo conteggio della linea non funziona. In che altro modo? –

+0

Quindi conta la quantità di byte scritti anziché la quantità di righe. Puoi conoscere in anticipo la dimensione del file in byte. – BalusC

0

Non faccio uso di leggere senza argomenti. È molto lento. Meglio leggerlo per il buffer e spostarlo rapidamente nel file.

Utilizzare bufferedInputStream perché supporta la lettura binaria.

Ed è tutto.

4

Questo è un ottimo articolo: http://java.sun.com/developer/technicalArticles/Programming/PerfTuning/

In sintesi, per grandi prestazioni, si dovrebbe:

  1. Evitare l'accesso al disco.
  2. Evitare l'accesso al sistema operativo sottostante.
  3. Evita chiamate di metodo.
  4. Evitare l'elaborazione di byte e caratteri singolarmente.

Ad esempio, per ridurre l'accesso al disco, è possibile utilizzare un buffer di grandi dimensioni. L'articolo descrive vari approcci.

12

È possibile considerare l'utilizzo di file mappati in memoria, tramite FileChannel s.

Generalmente molto più veloce per file di grandi dimensioni. Ci sono degli sconti sulle prestazioni che potrebbero rendere più lento, quindi YMMV.

risposta correlati: Java NIO FileChannel versus FileOutputstream performance/usefulness

+0

Se stai leggendo direttamente un file, molto probabilmente questo non ti darà molto. – james

+1

Ancora degno di nota :) –

+0

Generalmente * non * molto più veloce. L'ultima volta che l'ho confrontato, ho ottenuto il 20% di lettura. – EJP

3

ha a che fare in Java? Cioè ha bisogno di essere indipendente dalla piattaforma? In caso contrario, suggerirei di utilizzare il comando 'split' in * nix. Se lo volessi davvero, potresti eseguire questo comando tramite il tuo programma java. Anche se non l'ho ancora testato, immagino che funzioni più velocemente di qualsiasi implementazione di I/O Java che potresti ottenere.

0

A meno che non accidentalmente leggere in tutto il file di input, invece di leggerlo riga per riga, allora il vostro limite principale sarà la velocità del disco. Si consiglia di provare a iniziare con un file contenente 100 righe e scriverlo su 100 file diversi di una riga ciascuno e rendere il meccanismo di attivazione funzionante sul numero di righe scritte nel file corrente. Quel programma sarà facilmente scalabile alla tua situazione.

1

Sì. Penso anche che l'uso di read() con argomenti come la lettura (Char [], int init, int end) è un modo migliore per leggere un file di grandi dimensioni come ad (es: lettura (buffer, 0, buffer.length))

E ho anche sperimentato il problema dei valori di utilizzo del BufferedReader invece di BufferedInputStreamReader per un flusso di input di dati binari mancanti. Quindi, usare BufferedInputStreamReader è molto meglio in questo caso.

0

package all.is.well; 
 
import java.io.IOException; 
 
import java.io.RandomAccessFile; 
 
import java.util.concurrent.ExecutorService; 
 
import java.util.concurrent.Executors; 
 
import junit.framework.TestCase; 
 

 
/** 
 
* @author Naresh Bhabat 
 
* 
 
Following implementation helps to deal with extra large files in java. 
 
This program is tested for dealing with 2GB input file. 
 
There are some points where extra logic can be added in future. 
 

 

 
Pleasenote: if we want to deal with binary input file, then instead of reading line,we need to read bytes from read file object. 
 

 

 

 
It uses random access file,which is almost like streaming API. 
 

 

 
* **************************************** 
 
Notes regarding executor framework and its readings. 
 
Please note :ExecutorService executor = Executors.newFixedThreadPool(10); 
 

 
* \t for 10 threads:Total time required for reading and writing the text in 
 
*   :seconds 349.317 
 
* 
 
*   For 100:Total time required for reading the text and writing : seconds 464.042 
 
* 
 
*   For 1000 : Total time required for reading and writing text :466.538 
 
*   For 10000 Total time required for reading and writing in seconds 479.701 
 
* 
 
* 
 
*/ 
 
public class DealWithHugeRecordsinFile extends TestCase { 
 

 
\t static final String FILEPATH = "C:\\springbatch\\bigfile1.txt.txt"; 
 
\t static final String FILEPATH_WRITE = "C:\\springbatch\\writinghere.txt"; 
 
\t static volatile RandomAccessFile fileToWrite; 
 
\t static volatile RandomAccessFile file; 
 
\t static volatile String fileContentsIter; 
 
\t static volatile int position = 0; 
 

 
\t public static void main(String[] args) throws IOException, InterruptedException { 
 
\t \t long currentTimeMillis = System.currentTimeMillis(); 
 

 
\t \t try { 
 
\t \t \t fileToWrite = new RandomAccessFile(FILEPATH_WRITE, "rw");//for random write,independent of thread obstacles 
 
\t \t \t file = new RandomAccessFile(FILEPATH, "r");//for random read,independent of thread obstacles 
 
\t \t \t seriouslyReadProcessAndWriteAsynch(); 
 

 
\t \t } catch (IOException e) { 
 
\t \t \t // TODO Auto-generated catch block 
 
\t \t \t e.printStackTrace(); 
 
\t \t } 
 
\t \t Thread currentThread = Thread.currentThread(); 
 
\t \t System.out.println(currentThread.getName()); 
 
\t \t long currentTimeMillis2 = System.currentTimeMillis(); 
 
\t \t double time_seconds = (currentTimeMillis2 - currentTimeMillis)/1000.0; 
 
\t \t System.out.println("Total time required for reading the text in seconds " + time_seconds); 
 

 
\t } 
 

 
\t /** 
 
\t * @throws IOException 
 
\t * Something asynchronously serious 
 
\t */ 
 
\t public static void seriouslyReadProcessAndWriteAsynch() throws IOException { 
 
\t \t ExecutorService executor = Executors.newFixedThreadPool(10);//pls see for explanation in comments section of the class 
 
\t \t while (true) { 
 
\t \t \t String readLine = file.readLine(); 
 
\t \t \t if (readLine == null) { 
 
\t \t \t \t break; 
 
\t \t \t } 
 
\t \t \t Runnable genuineWorker = new Runnable() { 
 
\t \t \t \t @Override 
 
\t \t \t \t public void run() { 
 
\t \t \t \t \t // do hard processing here in this thread,i have consumed 
 
\t \t \t \t \t // some time and ignore some exception in write method. 
 
\t \t \t \t \t writeToFile(FILEPATH_WRITE, readLine); 
 
\t \t \t \t \t // System.out.println(" :" + 
 
\t \t \t \t \t // Thread.currentThread().getName()); 
 

 
\t \t \t \t } 
 
\t \t \t }; 
 
\t \t \t executor.execute(genuineWorker); 
 
\t \t } 
 
\t \t executor.shutdown(); 
 
\t \t while (!executor.isTerminated()) { 
 
\t \t } 
 
\t \t System.out.println("Finished all threads"); 
 
\t \t file.close(); 
 
\t \t fileToWrite.close(); 
 
\t } 
 

 
\t /** 
 
\t * @param filePath 
 
\t * @param data 
 
\t * @param position 
 
\t */ 
 
\t private static void writeToFile(String filePath, String data) { 
 
\t \t try { 
 
\t \t \t // fileToWrite.seek(position); 
 
\t \t \t data = "\n" + data; 
 
\t \t \t if (!data.contains("Randomization")) { 
 
\t \t \t \t return; 
 
\t \t \t } 
 
\t \t \t System.out.println("Let us do something time consuming to make this thread busy"+(position++) + " :" + data); 
 
\t \t \t System.out.println("Lets consume through this loop"); 
 
\t \t \t int i=1000; 
 
\t \t \t while(i>0){ 
 
\t \t \t 
 
\t \t \t \t i--; 
 
\t \t \t } 
 
\t \t \t fileToWrite.write(data.getBytes()); 
 
\t \t \t throw new Exception(); 
 
\t \t } catch (Exception exception) { 
 
\t \t \t System.out.println("exception was thrown but still we are able to proceeed further" 
 
\t \t \t \t \t + " \n This can be used for marking failure of the records"); 
 
\t \t \t //exception.printStackTrace(); 
 

 
\t \t } 
 

 
\t } 
 
}

Problemi correlati