2009-10-15 15 views
5

Tengo un archivo de registro que se actualiza cada segundo. Necesito leer el archivo de registro periódicamente, y una vez que hago una lectura, necesito almacenar la posición del puntero del archivo al final de la última línea que leí y en la siguiente lectura periódica debería comenzar desde ese punto.¿Manera eficiente de manejar punteros a archivos en Java? (Uso de BufferedReader con puntero de archivo)

Actualmente, estoy usando un archivo de acceso aleatorio en Java y utilizando el método getFilePointer() para obtener el valor de compensación y el método seek() para ir a la posición de compensación.

Sin embargo, he leído en la mayoría de los artículos e incluso las recomendaciones de doc de Java para usar BufferredReader para la lectura eficiente de un archivo. ¿Cómo puedo lograr esto (obtener el identificador de archivo y pasar a la última línea) usando un BufferedReader, o hay alguna otra manera eficiente de lograr esta tarea?

Respuesta

4

un par de maneras que debería funcionar:

  • abrir el archivo utilizando un FileInputStream, saltar() el número correspondiente de bytes, y luego envuelva el BufferedReader alrededor de la corriente (a través de un InputStreamReader);
  • abra el archivo (con FileInputStream o RandomAccessFile), llame a getChannel() en la ruta/RandomAccessFile para obtener un FileChannel subyacente, call position() en el canal, luego llame a Channels.newInputStream() para obtener un flujo de entrada de el canal, que puede pasar a InputStreamReader -> BufferedReader.

Honestamente no he perfilado estos para ver cuál es mejor en cuanto a rendimiento, pero debería ver cuál funciona mejor en su situación.

El problema con RandomAccessFile es que su método readLine() es muy ineficiente. Si es conveniente para usted leer desde el RAF y hacer su propio almacenamiento en búfer para dividir las líneas, no hay nada de malo en RAF per se-- solo que su readLine() está mal implementado

1

La solución de Neil Coffey es buena si están leyendo archivos de longitud fija. Sin embargo, para los archivos que tienen una longitud variable (los datos siguen llegando) existen algunos problemas con el uso de BufferedReader directamente en FileInputStream o FileChannel inputstream a través de un InputStreamReader. Para ex consideran los casos

  • 1) desea leer datos de alguna desviación de la longitud del archivo actual. Entonces usas BR en FileInputStream/FileChannel (a través de un InputStreamReader) y usas su método readLine. Pero mientras está ocupado leyendo los datos, digamos que se agregaron algunos datos que hacen que readLine de BF lea más datos de los esperados (la longitud del archivo anterior)

  • 2) Terminó de leer las líneas pero cuando intenta leer la longitud actual del archivo/posición del canal algunos datos se agregaron repentinamente, lo que hace que aumente la longitud actual del archivo/posición del canal, pero ya ha leído menos datos que este.

En los dos casos anteriores, es difícil conocer los datos reales que haya leído (no se puede simplemente utilizar la longitud de los datos leídos usando readLine porque omite algunos caracteres como retorno de carro)

Así es mejor leer los datos en bytes almacenados en búfer y usar un contenedor BufferedReader alrededor de esto.Escribí algunos métodos como éste

/** 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

tuve un problema similar, y he creado esta clase para tomar las líneas de BufferedStream, y contar la cantidad de bytes que ha leído hasta ahora mediante el uso de getBytes(). Asumimos que el separador de línea tiene un solo byte de manera predeterminada, y volver a la instancia de BufferedReaderseek() para trabajar.

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; 
    } 
} 
Cuestiones relacionadas