2009-10-15 2 views
5

Mam plik dziennika, który jest aktualizowany co sekundę. Muszę okresowo odczytywać plik dziennika, a po wykonaniu odczytu, muszę zapisać pozycję wskaźnika pliku na końcu ostatniej linii, którą przeczytałem, aw następnym okresowym czytaniu powinienem zacząć od tego punktu.Wydajny sposób obsługi wskaźników plików w Javie? (Używanie BufferedReader ze wskaźnikiem pliku)

Obecnie używam pliku dostępu losowego w Javie i metodą getFilePointer(), aby uzyskać wartość przesunięcia i metodę seek(), aby przejść do pozycji przesunięcia.

Jednak przeczytałem w większości artykułów, a nawet zalecenia doktora w języku Java, aby użyć BufferredReader do wydajnego odczytu pliku. Jak mogę to osiągnąć (pobierając szablon i przechodząc do ostatniej linii) za pomocą BufferedReader, czy jest jakiś inny skuteczny sposób na osiągnięcie tego zadania?

Odpowiedz

4

Kilka sposobów, które powinno działać:

  • otworzyć plik za pomocą FileInputStream, pomiń() odpowiednią ilość bajtów, a następnie owinąć BufferedReader wokół strumienia (za pośrednictwem InputStreamReader);
  • otwórz plik (z FileInputStream lub RandomAccessFile), wywołaj getChannel() w strumieniu/RandomAccessFile, aby uzyskać bazowy FileChannel, pozycję call() na kanale, a następnie wywołaj Channels.newInputStream(), aby uzyskać strumień wejściowy z kanał, który można przekazać do InputStreamReader -> BufferedReader.

Nie opisałem ich w rzetelny sposób, aby sprawdzić, która z nich jest lepsza pod względem wydajności, ale powinieneś zobaczyć, która z nich działa lepiej w Twojej sytuacji.

Problem z RandomAccessFile polega w zasadzie na tym, że jego metoda readLine() jest bardzo nieefektywna. Jeśli wygodnie jest czytać z RAF i robić własne buforowanie, aby podzielić linie, to nie ma nic złego w RAF per se - tylko to, że jego readLine() jest źle zaimplementowane

1

Rozwiązanie Neila Coffeya jest dobre, jeśli czytają pliki o stałej długości. Jednak w przypadku plików o zmiennej długości (dane przychodzą) wystąpią problemy z używaniem BufferedReader bezpośrednio w strumieniu wejściowym FileInputStream lub FileChannel za pośrednictwem InputStreamReader. Dla ex rozważyć przypadki

  • 1) Chcesz odczytać dane z niektórych przesunięcie do bieżącej długości pliku. Używasz BR w FileInputStream/FileChannel (przez InputStreamReader) i używasz metody readLine. Ale gdy jesteś zajęty czytaniem danych, powiedz, że dodano pewne dane, które powodują, że readLine BF odczytuje więcej danych niż oczekiwałeś (poprzednia długość pliku):

  • 2) Skończyłeś czytać, ale kiedy próbujesz przeczytać bieżąca długość pliku/pozycja kanału niektóre dane zostały dodane nagle, co powoduje wzrost aktualnej długości pliku/pozycji kanału, ale już przeczytałeś mniej danych niż to.

W obu powyższych przypadkach trudno jest poznać rzeczywiste dane masz prawa odczytu (nie można po prostu użyć długość odczyt danych za pomocą readLine ponieważ pomija niektóre znaki, takie jak powrót karetki)

Tak lepiej jest odczytać dane w zbuforowanych bajtach i używać w tym celu opakowania BufferedReader.Napisałem kilka metod, jak ten

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

miałem podobny problem, a ja stworzył tę klasę do podjęcia linie z BufferedStream i policzyć ile bajtów masz tak daleko za pomocą getBytes() czytać. Zakładamy, że separator linii ma domyślnie pojedynczy bajt i ponownie uruchamiamy BufferedReader dla seek() do pracy.

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