Java: чтение последних n строк ОГРОМНОГО файла - PullRequest
36 голосов
/ 08 ноября 2010

Я хочу прочитать последние n строк очень большого файла без чтения всего файла в какой-либо области буфера / памяти с помощью Java.

Я просмотрел API-интерфейсы JDK и ввод-вывод Apache Commons и вижу:не в состоянии найти тот, который подходит для этой цели.

Я думал о том, как tail или менее делает это в UNIX.Я не думаю, что они загружают весь файл, а затем показывают последние несколько строк файла.Должен быть похожий способ сделать то же самое в Java.

Ответы [ 10 ]

27 голосов
/ 08 ноября 2010

Если вы используете RandomAccessFile, вы можете использовать length и seek, чтобы добраться до определенной точки в конце файл, а затем читать вперед оттуда.

Если вы обнаружите, что строк недостаточно, вернитесь назад с этой точки и попробуйте снова. Как только вы выяснили, где начинается N последняя строка, вы можете искать там и просто читать и печатать.

Исходное предположение о наилучшем предположении может быть сделано на основе ваших свойств данных. Например, если это текстовый файл, возможно, длина строк не будет превышать в среднем 132, поэтому, чтобы получить последние пять строк, начните с 660 символов до конца. Затем, если вы ошиблись, попробуйте еще раз на 1320 (вы можете даже использовать то, что вы узнали из последних 660 символов, чтобы отрегулировать это - пример: если эти 660 символов были всего лишь тремя строками, следующая попытка может быть 660/3 * 5 плюс на всякий случай немного лишнее).

27 голосов
/ 02 сентября 2014

Я нашел самый простой способ сделать это, используя ReversedLinesFileReader из apache commons-io api.Этот метод даст вам строку снизу вверх файла, и вы можете указать значение n_lines, чтобы указать номер строки.

import org.apache.commons.io.input.ReversedLinesFileReader;


File file = new File("D:\\file_name.xml");
int n_lines = 10;
int counter = 0; 
ReversedLinesFileReader object = new ReversedLinesFileReader(file);
while(counter < n_lines) {
    System.out.println(object.readLine());
    counter++;
}
19 голосов
/ 08 ноября 2010

RandomAccessFile - хорошее место для начала, как описано в других ответах.Существует одно важное предостережение .

Если ваш файл не закодирован однобайтовым кодированием, метод readLine() не будетработать на тебя.И readUTF() не будет работать ни при каких обстоятельствах.(Он читает строку, которой предшествует число символов ...)

Вместо этого вам необходимо убедиться, что вы ищете маркеры конца строки таким образом, чтобы соблюдать границы символов кодировки.Для кодировок фиксированной длины (например, разновидностей UTF-16 или UTF-32) вам необходимо извлечь символы, начиная с позиций байтов, которые делятся на размер символов в байтах.Для кодировок переменной длины (например, UTF-8) вам необходимо найти байт, в котором должен быть первым байтом символа.

В случае UTF-8 первый байт символа будет 0xxxxxxx или 110xxxxx или 1110xxxx или 11110xxx.Все остальное является либо вторым / третьим байтом, либо недопустимой последовательностью UTF-8.См. Стандарт Unicode, Версия 5.2, Глава 3.9 , Таблица 3-7.Это означает, что, как отмечается в комментариях, любые байты 0x0A и 0x0D в правильно закодированном потоке UTF-8 будут представлять символ LF или CR.Таким образом, простой подсчет байтов 0x0A и 0x0D является допустимой стратегией реализации (для UTF-8), если мы можем предположить, что другие виды разделителя строк Unicode (0x2028, 0x2029 и 0x0085) не используются.Вы не можете этого предположить, тогда код будет более сложным.

Определив правильную границу символа, вы можете просто вызвать new String(...), передавая байтовый массив, смещение, число и кодировку, а затем повторновызовите String.lastIndexOf(...) для подсчета конца строки.

4 голосов
/ 18 сентября 2013

Я нашел RandomAccessFile и другие классы Buffer Reader слишком медленными для меня. Ничто не может быть быстрее, чем tail -<#lines>. Так что это было лучшее решение для меня.

public String getLastNLogLines(File file, int nLines) {
    StringBuilder s = new StringBuilder();
    try {
        Process p = Runtime.getRuntime().exec("tail -"+nLines+" "+file);
        java.io.BufferedReader input = new java.io.BufferedReader(new java.io.InputStreamReader(p.getInputStream()));
        String line = null;
    //Here we first read the next line into the variable
    //line and then check for the EOF condition, which
    //is the return value of null
    while((line = input.readLine()) != null){
            s.append(line+'\n');
        }
    } catch (java.io.IOException e) {
        e.printStackTrace();
    }
    return s.toString();
}
2 голосов
/ 19 февраля 2013

CircularFifoBuffer от Apache Commons.ответ на похожий вопрос на Как прочитать последние 5 строк файла .txt в java

Обратите внимание, что в Apache Commons Collections 4 этот класс, похоже, был переименован в CircularFifoQueue

1 голос
/ 13 декабря 2017

У меня была похожая проблема, но я не понял другого решения.

Я использовал это. Я надеюсь, что это простой код.

// String filePathName = (direction and file name).
File f = new File(filePathName);
long fileLength = f.length(); // Take size of file [bites].
long fileLength_toRead = 0;
if (fileLength > 2000) {
    // My file content is a table, I know one row has about e.g. 100 bites / characters. 
    // I used 1000 bites before file end to point where start read.
    // If you don't know line length, use @paxdiablo advice.
    fileLength_toRead = fileLength - 1000;
}
try (RandomAccessFile raf = new RandomAccessFile(filePathName, "r")) { // This row manage open and close file.
    raf.seek(fileLength_toRead); // File will begin read at this bite. 
    String rowInFile = raf.readLine(); // First readed line usualy is not whole, I needn't it.
    rowInFile = raf.readLine();
    while (rowInFile != null) {
        // Here I can readed lines (rowInFile) add to String[] array or ArriyList<String>.
        // Later I can work with rows from array - last row is sometimes empty, etc.
        rowInFile = raf.readLine();
    }
}
catch (IOException e) {
    //
}
1 голос
/ 05 октября 2017

ReversedLinesFileReader можно найти в java-библиотеке Apache Commons IO .

    int n_lines = 1000;
    ReversedLinesFileReader object = new ReversedLinesFileReader(new File(path));
    String result="";
    for(int i=0;i<n_lines;i++){
        String line=object.readLine();
        if(line==null)
            break;
        result+=line;
    }
    return result;
1 голос
/ 08 ноября 2010

A RandomAccessFile разрешает поиск (http://download.oracle.com/javase/1.4.2/docs/api/java/io/RandomAccessFile.html). Метод File.length возвращает размер файла. Проблема заключается в определении количества строк. Для этого вы можете искать в конце файла и читать в обратном направлении пока вы не наберете нужное количество строк.

0 голосов
/ 22 октября 2018

Вот работа для этого.

    private static void printLastNLines(String filePath, int n) {
    File file = new File(filePath);
    StringBuilder builder = new StringBuilder();
    try {
        RandomAccessFile randomAccessFile = new RandomAccessFile(filePath, "r");
        long pos = file.length() - 1;
        randomAccessFile.seek(pos);

        for (long i = pos - 1; i >= 0; i--) {
            randomAccessFile.seek(i);
            char c = (char) randomAccessFile.read();
            if (c == '\n') {
                n--;
                if (n == 0) {
                    break;
                }
            }
            builder.append(c);
        }
        builder.reverse();
        System.out.println(builder.toString());
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
}
0 голосов
/ 31 августа 2011

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

public static void tail(File src, OutputStream out, int maxLines) throws FileNotFoundException, IOException {
    BufferedReader reader = new BufferedReader(new FileReader(src));
    String[] lines = new String[maxLines];
    int lastNdx = 0;
    for (String line=reader.readLine(); line != null; line=reader.readLine()) {
        if (lastNdx == lines.length) {
            lastNdx = 0;
        }
        lines[lastNdx++] = line;
    }

    OutputStreamWriter writer = new OutputStreamWriter(out);
    for (int ndx=lastNdx; ndx != lastNdx-1; ndx++) {
        if (ndx == lines.length) {
            ndx = 0;
        }
        writer.write(lines[ndx]);
        writer.write("\n");
    }

    writer.flush();
}
...