Быстро прочитать последнюю строку текстового файла? - PullRequest
57 голосов
/ 26 марта 2009

Какой самый быстрый и эффективный способ чтения последней строки текста из [очень, очень большого] файла в Java?

Ответы [ 8 ]

81 голосов
/ 06 сентября 2011

Ниже приведены две функции, одна из которых возвращает последнюю непустую строку файла без загрузки или пошагового выполнения по всему файлу, а вторая возвращает последние N строк файла, не проходя по всему файлу. :

То, что делает хвост, это масштабирование прямо до последнего символа файла, затем шаг за шагом, символ за символом, запись того, что он видит, пока не обнаружит разрыв строки. Как только он находит разрыв строки, он выходит из цикла. Переворачивает то, что было записано, бросает его в строку и возвращает. 0xA - это новая строка, а 0xD - возврат каретки.

Если ваши окончания строки \r\n или crlf или какой-либо другой "двойной перевод новой строки в стиле новой строки", то вам нужно будет указать n * 2 строки, чтобы получить последние n строк, потому что она рассчитывает 2 строки для каждой строки.

public String tail( File file ) {
    RandomAccessFile fileHandler = null;
    try {
        fileHandler = new RandomAccessFile( file, "r" );
        long fileLength = fileHandler.length() - 1;
        StringBuilder sb = new StringBuilder();

        for(long filePointer = fileLength; filePointer != -1; filePointer--){
            fileHandler.seek( filePointer );
            int readByte = fileHandler.readByte();

            if( readByte == 0xA ) {
                if( filePointer == fileLength ) {
                    continue;
                }
                break;

            } else if( readByte == 0xD ) {
                if( filePointer == fileLength - 1 ) {
                    continue;
                }
                break;
            }

            sb.append( ( char ) readByte );
        }

        String lastLine = sb.reverse().toString();
        return lastLine;
    } catch( java.io.FileNotFoundException e ) {
        e.printStackTrace();
        return null;
    } catch( java.io.IOException e ) {
        e.printStackTrace();
        return null;
    } finally {
        if (fileHandler != null )
            try {
                fileHandler.close();
            } catch (IOException e) {
                /* ignore */
            }
    }
}

Но вам, вероятно, не нужна последняя строка, вам нужны последние N строк, поэтому используйте вместо этого:

public String tail2( File file, int lines) {
    java.io.RandomAccessFile fileHandler = null;
    try {
        fileHandler = 
            new java.io.RandomAccessFile( file, "r" );
        long fileLength = fileHandler.length() - 1;
        StringBuilder sb = new StringBuilder();
        int line = 0;

        for(long filePointer = fileLength; filePointer != -1; filePointer--){
            fileHandler.seek( filePointer );
            int readByte = fileHandler.readByte();

             if( readByte == 0xA ) {
                if (filePointer < fileLength) {
                    line = line + 1;
                }
            } else if( readByte == 0xD ) {
                if (filePointer < fileLength-1) {
                    line = line + 1;
                }
            }
            if (line >= lines) {
                break;
            }
            sb.append( ( char ) readByte );
        }

        String lastLine = sb.reverse().toString();
        return lastLine;
    } catch( java.io.FileNotFoundException e ) {
        e.printStackTrace();
        return null;
    } catch( java.io.IOException e ) {
        e.printStackTrace();
        return null;
    }
    finally {
        if (fileHandler != null )
            try {
                fileHandler.close();
            } catch (IOException e) {
            }
    }
}

Вызовите вышеуказанные методы, как это:

File file = new File("D:\\stuff\\huge.log");
System.out.println(tail(file));
System.out.println(tail2(file, 10));

Внимание * * 1023 На диком западе юникода этот код может привести к неправильному выводу этой функции. Например "Мэри?" Вместо "Мэри". Символы с шляпами, акцентами, китайскими иероглифами и т. Д. Могут привести к неправильному выводу, поскольку акценты добавляются в качестве модификаторов после символа. Реверсирование составных символов изменяет характер личности персонажа при обращении. Вам придется выполнить полный набор тестов на всех языках, с которыми вы планируете его использовать.

Для получения дополнительной информации об этой проблеме обращения Unicode прочитайте это: http://msmvps.com/blogs/jon_skeet/archive/2009/11/02/omg-ponies-aka-humanity-epic-fail.aspx

28 голосов
/ 01 марта 2014

Apache Commons имеет реализацию, использующую RandomAccessFile .

Это называется ReversedLinesFileReader .

18 голосов
/ 26 марта 2009

Посмотрите на мой ответ на подобный вопрос для C # . Код был бы очень похож, хотя поддержка кодирования в Java несколько иная.

В общем, это не очень легко сделать вообще. Как указывает MSalter, UTF-8 позволяет легко определить \r или \n, поскольку представление этих символов в UTF-8 точно такое же, как в ASCII, и эти байты не будут иметь многобайтовый символ.

Таким образом, по сути, возьмите буфер (скажем) 2K и постепенно читайте в обратном направлении (перейдите к 2K до того, как вы были раньше, прочитайте следующие 2K), проверяя завершение строки. Затем перейдите в нужное место в потоке, создайте InputStreamReader в верхней части и BufferedReader в верхней части. Тогда просто позвоните BufferedReader.readLine().

3 голосов
/ 26 марта 2009

Использование FileReader или FileInputStream не будет работать - вам придется использовать либо FileChannel , либо RandomAccessFile , чтобы перебрать файл в обратном направлении от конца. Однако, как сказал Джон, проблемы с кодировками будут проблемой.

1 голос
/ 25 сентября 2013

Вы можете легко изменить приведенный ниже код для печати последней строки.

MemoryMappedFile для печати последних 5 строк:

private static void printByMemoryMappedFile(File file) throws FileNotFoundException, IOException{
        FileInputStream fileInputStream=new FileInputStream(file);
        FileChannel channel=fileInputStream.getChannel();
        ByteBuffer buffer=channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size());
        buffer.position((int)channel.size());
        int count=0;
        StringBuilder builder=new StringBuilder();
        for(long i=channel.size()-1;i>=0;i--){
            char c=(char)buffer.get((int)i);
            builder.append(c);
            if(c=='\n'){
                if(count==5)break;
                count++;
                builder.reverse();
                System.out.println(builder.toString());
                builder=null;
                builder=new StringBuilder();
            }
        }
        channel.close();
    }

RandomAccessFile для печати последних 5 строк:

private static void printByRandomAcessFile(File file) throws FileNotFoundException, IOException{
        RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r");
        int lines = 0;
        StringBuilder builder = new StringBuilder();
        long length = file.length();
        length--;
        randomAccessFile.seek(length);
        for(long seek = length; seek >= 0; --seek){
            randomAccessFile.seek(seek);
            char c = (char)randomAccessFile.read();
            builder.append(c);
            if(c == '\n'){
                builder = builder.reverse();
                System.out.println(builder.toString());
                lines++;
                builder = null;
                builder = new StringBuilder();
                if (lines == 5){
                    break;
                }
            }

        }
    }
0 голосов
/ 17 сентября 2018

Насколько я знаю, самый быстрый способ прочитать последнюю строку текстового файла - использовать класс Apache FileUtils, который находится в "org.apache.commons.io". У меня есть файл с двумя миллионами строк, и с помощью этого класса мне понадобилось менее одной секунды, чтобы найти последнюю строку. Вот мой код:

LineIterator lineIterator = FileUtils.lineIterator(newFile(filePath),"UTF-8");
String lastLine="";
while (lineIterator.hasNext()){
 lastLine=  lineIterator.nextLine();
}
0 голосов
/ 08 мая 2015
try(BufferedReader reader = new BufferedReader(new FileReader(reqFile))) {

    String line = null;

    System.out.println("======================================");

    line = reader.readLine();       //Read Line ONE
    line = reader.readLine();       //Read Line TWO
    System.out.println("first line : " + line);

    //Length of one line if lines are of even length
    int len = line.length();       

    //skip to the end - 3 lines
    reader.skip((reqFile.length() - (len*3)));

    //Searched to the last line for the date I was looking for.

    while((line = reader.readLine()) != null){

        System.out.println("FROM LINE : " + line);
        String date = line.substring(0,line.indexOf(","));

        System.out.println("DATE : " + date);      //BAM!!!!!!!!!!!!!!
    }

    System.out.println(reqFile.getName() + " Read(" + reqFile.length()/(1000) + "KB)");
    System.out.println("======================================");
} catch (IOException x) {
    x.printStackTrace();
}
0 голосов
/ 26 марта 2009

В C # вы должны иметь возможность установить позицию потока:

От: http://bytes.com/groups/net-c/269090-streamreader-read-last-line-text-file

using(FileStream fs = File.OpenRead("c:\\file.dat"))
{
    using(StreamReader sr = new StreamReader(fs))
    {
        sr.BaseStream.Position = fs.Length - 4;
        if(sr.ReadToEnd() == "DONE")
            // match
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...