2010-05-20 58 views
11

我正在使用集成日誌文件查看器的軟件產品。問題是,對於真正的大文件,它很慢並且不穩定,因爲它在查看日誌文件時將整個文件讀入內存。我想寫一個新的日誌文件查看器來解決這個問題。如何爲大日誌文件編寫Java文本文件查看器

爲大型文本文件編寫查看器的最佳做法是什麼?記事本++和VIM等編輯如何使用它?我正在考慮將緩衝雙向文本流讀取器與Java的TableModel一起使用。我是否按照正確的思路思考,併爲Java提供這樣的流實現?

編輯:是否值得運行一次文件來索引每行文本的開始位置,以便知道在哪裏尋找?我可能需要大量的行,所以可能必須至少掃描一次文件?

編輯2:我已將我的實現添加到下面的答案。請對此進行評論或對其進行編輯以幫助我/我們實現更好的實踐或以其他方式提供您自己的最佳做法。

回答

4

我不確定NotePad ++實際上是否實現隨機訪問,但我認爲這是一條路,特別是使用日誌文件查看器時,這意味着它只能讀取。

由於您的日誌查看器將是隻讀的,因此您可以使用只讀的內存映射文件「流」。在Java中,這是FileChannel

然後根據需要在文件中跳轉,並在屏幕上呈現數據的滾動窗口。

FileChannel的優點之一是併發線程可以打開文件,並且讀取不會影響當前文件指針。因此,如果您在另一個線程中追加到日誌文件,它將不會受到影響。

另一個優點是您可以隨時調用FileChannel的size方法來獲取文件大小。

將內存直接映射到某些文本編輯器允許的隨機訪問文件(如HxD和UltraEdit)的問題是,任何更改都會直接影響文件。因此,更改是立即的(寫緩存除外),這是用戶通常不需要的。相反,用戶通常不希望在點擊「保存」之前進行更改。然而,由於這只是一個觀衆,你不會有同樣的擔憂。

+0

謝謝,我也看到的RandomAccessFile除了FileChannel這可能證明是有用的 – 2010-05-20 13:46:23

2

一個典型的方法是使用一個可查找的文件讀取器,在日誌中記錄一個行偏移量的索引,然後根據請求僅向文件的一部分顯示一個窗口。

這會減少您在快速調用時需要的數據,並且不會加載其中99%的內容當前不可見的小部件。

0

爲了您的方便,我發佈自己的測試實施(在遵循Marcus Adams和msw的建議之後),並提供進一步的評論和批評。它相當快。

我還沒有打擾Unicode編碼的安全性。我想這將是我的下一個問題。對此非常歡迎的任何提示。

class LogFileTableModel implements TableModel { 

    private final File f; 
    private final int lineCount; 
    private final String errMsg; 
    private final Long[] index; 
    private final ByteBuffer linebuf = ByteBuffer.allocate(1024); 
    private FileChannel chan; 

    public LogFileTableModel(String filename) { 
     f = new File(filename); 
     String m; 
     int l = 1; 
     Long[] idx = new Long[] {}; 
     try { 
      FileInputStream in = new FileInputStream(f); 
      chan = in.getChannel(); 
      m = null; 
      idx = buildLineIndex(); 
      l = idx.length; 
     } catch (IOException e) { 
      m = e.getMessage(); 
     } 
     errMsg = m; 
     lineCount = l; 
     index = idx; 
    } 

    private Long[] buildLineIndex() throws IOException { 
     List<Long> idx = new LinkedList<Long>(); 
     idx.add(0L); 

     ByteBuffer buf = ByteBuffer.allocate(8 * 1024); 
     long offset = 0; 
     while (chan.read(buf) != -1) { 
      int len = buf.position(); 
      buf.rewind();    
      int pos = 0; 
      byte[] bufA = buf.array(); 
      while (pos < len) { 
       byte c = bufA[pos++]; 
       if (c == '\n') 
        idx.add(offset + pos); 
      } 
      offset = chan.position(); 
     } 
     System.out.println("Done Building index"); 
     return idx.toArray(new Long[] {}); 
    } 

    @Override 
    public int getColumnCount() { 
     return 2; 
    } 

    @Override 
    public int getRowCount() { 
     return lineCount; 
    } 

    @Override 
    public String getColumnName(int columnIndex) { 
     switch (columnIndex) { 
     case 0: 
      return "#"; 
     case 1: 
      return "Name"; 
     } 
     return ""; 
    } 

    @Override 
    public Object getValueAt(int rowIndex, int columnIndex) { 
     switch (columnIndex) { 
      case 0:     
       return String.format("%3d", rowIndex); 
      case 1: 
       if (errMsg != null) 
        return errMsg; 
       try { 
        Long pos = index[rowIndex]; 
        chan.position(pos); 
        chan.read(linebuf); 
        linebuf.rewind(); 
        if (rowIndex == lineCount - 1) 
         return new String(linebuf.array()); 
        else  
         return new String(linebuf.array(), 0, (int)(long)(index[rowIndex+1]-pos)); 
       } catch (Exception e) { 
        return "Error: "+ e.getMessage(); 
       } 
     }    
     return "a"; 
    } 

    @Override 
    public Class<?> getColumnClass(int columnIndex) { 
     return String.class; 
    } 

    // ... other methods to make interface complete 


} 
+0

嗯,好吧,好像我的實現是UTF-8安全的,因爲UTF-8固有的自同步岬的。檢查'\ n'是二進制00100000在UTF-8中是唯一的。所有字節是多字節序列的一部分,至少有8位。 – 2010-05-24 10:09:49