2009-10-15 14 views
5

Ho un file di registro che viene aggiornato ogni secondo. Ho bisogno di leggere il file di registro periodicamente, e una volta che faccio una lettura, ho bisogno di memorizzare la posizione del puntatore del file alla fine dell'ultima riga che ho letto e nella successiva lettura periodica dovrei iniziare da quel punto.Modo efficiente di gestire i puntatori di file in Java? (Utilizzo di BufferedReader con puntatore del file)

Attualmente, sto utilizzando un file di accesso casuale in Java e utilizzo il metodo getFilePointer() per ottenere il valore di offset e il metodo seek() per andare alla posizione di offset.

Tuttavia, ho letto nella maggior parte degli articoli e anche i consigli del documento Java per utilizzare BufferredReader per la lettura efficiente di un file. Come posso ottenere questo risultato (ottenendo il filepointer e spostandomi sull'ultima riga) utilizzando un BufferedReader o esiste un altro modo efficace per ottenere questo compito?

risposta

4

un paio di modi che dovrebbe funzionare:

  • aprire il file utilizzando un FileInputStream, saltare() il relativo numero di byte, quindi avvolgere il BufferedReader intorno al torrente (tramite un InputStreamReader);
  • aprire il file (con FileInputStream o RandomAccessFile), chiamare getChannel() sullo stream/RandomAccessFile per ottenere un FileChannel sottostante, chiamare position() sul canale, quindi chiamare Channels.newInputStream() per ottenere un flusso di input da il canale, che puoi passare a InputStreamReader -> BufferedReader.

Non ho onestamente profilato questi per vedere quale è meglio per le prestazioni, ma dovresti vedere quale funziona meglio nella tua situazione.

Il problema con RandomAccessFile è essenzialmente che il suo metodo readLine() è molto inefficiente. Se è conveniente leggere il RAF e fare il buffering per dividere le linee, allora non c'è niente di sbagliato in RAF di per sé-- solo che readLine() è male implementato

1

La soluzione di Neil Coffey è buona se stanno leggendo file di lunghezza fissa. Tuttavia, per i file che hanno una lunghezza variabile (i dati continuano ad arrivare) ci sono alcuni problemi con l'utilizzo di BufferedReader direttamente su FileInputStream o Filestream inputstream tramite un InputStreamReader. Per ex considerano i casi

  • 1) si desidera leggere i dati da un po 'di offset per la lunghezza del file corrente. Quindi tu usi BR su FileInputStream/FileChannel (tramite un InputStreamReader) e usa il suo metodo readLine. Ma mentre si è occupato a leggere i dati lasciano dicono alcuni dati ricevuti aggiunto che provoca readLine di BF di leggere più dati di quanto ti aspettavi (il precedente lunghezza del file)

  • 2) Hai finito readLine cose, ma quando si tenta di leggere la lunghezza corrente del file/posizione del canale alcuni dati sono stati aggiunti improvvisamente, il che fa aumentare la lunghezza del file corrente/la posizione del canale, ma avete già letto meno dati di questo.

In entrambi i casi di cui sopra, è difficile conoscere i dati effettivi di aver letto (non si può semplicemente utilizzare la lunghezza dei dati letti utilizzando readLine perché salta alcuni caratteri come ritorno a capo)

Così è meglio leggere i dati in byte bufferizzati e utilizzare un wrapper BufferedReader attorno a questo.Ho scritto alcuni metodi come questo

/** Read data from offset to length bytes in RandomAccessFile using BufferedReader 
* @param offset 
* @param length 
* @param accessFile 
* @throws IOException 
*/ 
    public static void readBufferedLines(long offset, long length, RandomAccessFile accessFile) throws IOException{ 
    if(accessFile == null) return; 
    int bufferSize = BYTE_BUFFER_SIZE;// constant say 4096 

    if(offset < length && offset >= 0){ 
     int index = 1; 
     long curPosition = offset; 
     /* 
     * iterate (length-from)/BYTE_BUFFER_SIZE times to read into buffer no matter where new line occurs 
     */ 
     while((curPosition + (index * BYTE_BUFFER_SIZE)) < length){   

      accessFile.seek(offset); // seek to last parsed data rather than last data read in to buffer 

      byte[] buf = new byte[bufferSize]; 
      int read = accessFile.read(buf, 0, bufferSize); 
      index++;// Increment whether or not read successful 

      if(read > 0){ 

       int lastnewLine = getLastLine(read,buf); 

       if(lastnewLine <= 0){ // no new line found in the buffer reset buffer size and continue 
        bufferSize = bufferSize+read; 
        continue; 

       } 
       else{ 
        bufferSize = BYTE_BUFFER_SIZE; 
       } 

       readLine(buf, 0, lastnewLine); // read the lines from buffer and parse the line 

       offset = offset+lastnewLine; // update the last data read 

      } 

     } 



     // Read last chunk. The last chunk size in worst case is the total file when no newline occurs 
     if(offset < length){ 

      accessFile.seek(offset); 
      byte[] buf = new byte[(int) (length-offset)]; 
      int read = accessFile.read(buf, 0, buf.length); 

      if(read > 0){ 

       readLine(buf, 0, read); 

       offset = offset+read; // update the last data read 


      } 
     } 


    } 

} 

private static void readLine(byte[] buf, int from , int lastnewLine) throws IOException{ 

    String readLine = ""; 
    BufferedReader reader = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(buf,from,lastnewLine))); 
    while((readLine = reader.readLine()) != null){ 
     //do something with readLine 
     System.out.println(readLine); 
    } 
    reader.close(); 
} 


private static int getLastLine(int read, byte[] buf) { 
    if(buf == null) return -1; 
    if(read > buf.length) read = buf.length; 
    while(read > 0 && !(buf[read-1] == '\n' || buf[read-1] == '\r')) read--;  
    return read; 
} 
public static void main(String[] args) throws IOException { 
    RandomAccessFile accessFile = new RandomAccessFile("C:/sri/test.log", "r"); 
    readBufferedLines(0, accessFile.length(), accessFile); 
    accessFile.close(); 

} 
0

Ho avuto un problema simile, e ho creato questa classe per prendere le linee da BufferedStream, e contare quanti byte che avete letto finora utilizzando getBytes(). Assumiamo che il separatore di riga abbia un singolo byte per impostazione predefinita e ripetiamo l'istanza di BufferedReader per seek().

public class FileCounterIterator { 

    public Long position() { 
     return _position; 
    } 

    public Long fileSize() { 
     return _fileSize; 
    } 

    public FileCounterIterator newlineLength(Long newNewlineLength) { 
     this._newlineLength = newNewlineLength; 
     return this; 
    } 

    private Long _fileSize = 0L; 
    private Long _position = 0L; 
    private Long _newlineLength = 1L; 
    private RandomAccessFile fp; 
    private BufferedReader itr; 

    public FileCounterIterator(String filename) throws IOException { 
     fp = new RandomAccessFile(filename, "r"); 
     _fileSize = fp.length(); 
     this.seek(0L); 
    } 

    public FileCounterIterator seek(Long newPosition) throws IOException { 
     this.fp.seek(newPosition); 
     this._position = newPosition; 
     itr = new BufferedReader(new InputStreamReader(new FileInputStream(fp.getFD()))); 
     return this; 
    } 

    public Boolean hasNext() throws IOException { 
     return this._position < this._fileSize; 
    } 

    public String readLine() throws IOException { 
     String nextLine = itr.readLine(); 
     this._position += nextLine.getBytes().length + _newlineLength; 
     return nextLine; 
    } 
} 
Problemi correlati