Как мне написать средство просмотра текстовых файлов Java для больших файлов журнала - PullRequest
11 голосов
/ 20 мая 2010

Я работаю над программным продуктом со встроенным средством просмотра файлов журнала. Проблема в том, что он работает медленно и нестабильно для действительно больших файлов, потому что он читает весь файл в память при просмотре файла журнала. Я хочу написать новый просмотрщик файлов журнала, который решает эту проблему.

Каковы лучшие практики для написания зрителей для больших текстовых файлов? Как редакторы, такие как notepad ++ и VIM, позволяют это сделать? Я думал об использовании буферизированного двунаправленного считывателя текстового потока вместе с TableModel Java. Думал ли я в правильном ключе и доступны ли такие реализации потоков для Java?

Редактировать: Стоит ли пробежаться по файлу один раз, чтобы индексировать позиции начала каждой строки текста, чтобы знать, где искать? Вероятно, мне понадобится количество строк, поэтому, возможно, придется сканировать файл хотя бы один раз?

Edit2: я добавил свою реализацию к ответу ниже. Пожалуйста, прокомментируйте или отредактируйте его, чтобы помочь мне / нам прийти к более эффективной практике или предоставить свой собственный.

Ответы [ 3 ]

4 голосов
/ 20 мая 2010

Я не уверен, что NotePad ++ на самом деле реализует произвольный доступ, но я думаю, что это путь, особенно со средством просмотра файлов журнала, которое подразумевает, что он будет только для чтения.

Поскольку ваша программа просмотра журналов будет доступна только для чтения, вы можете использовать только для чтения произвольный доступ файл с отображением в памяти "stream". В Java это FileChannel .

Затем просто прыгайте в файл по мере необходимости и визуализируйте на экране просто окно прокрутки данных.

Одним из преимуществ FileChannel является то, что одновременные потоки могут открывать файл, и чтение не влияет на текущий указатель файла. Поэтому, если вы добавляете файл журнала в другой поток, это не будет затронуто.

Еще одним преимуществом является то, что вы можете вызвать метод размера FileChannel, чтобы получить размер файла в любой момент.

Проблема с отображением памяти непосредственно в файл произвольного доступа, который допускают некоторые текстовые редакторы (например, HxD и UltraEdit), заключается в том, что любые изменения напрямую влияют на файл. Следовательно, изменения происходят немедленно (за исключением кэширования записи), чего обычно не хотят пользователи. Вместо этого пользователи обычно не хотят вносить свои изменения, пока они не нажмут кнопку «Сохранить». Однако, поскольку это просто зритель, у вас нет таких проблем.

2 голосов
/ 20 мая 2010

Типичный подход состоит в том, чтобы использовать средство поиска файлов с возможностью поиска, сделать один проход через журнал, записывающий индекс смещений строк, а затем представить только окно для части файла в соответствии с запросом.

Это сокращает объем данных, необходимых для быстрого вызова, и не загружает виджет, где 99% его содержимого в данный момент не отображается.

0 голосов
/ 21 мая 2010

Я публикую свою реализацию теста (после следования советам Маркуса Адамса и 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


}
...