Быстро прочитать последнюю строку текстового файла?


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

8 55

8 ответов:

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

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

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

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

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

если ваши окончания строки \r\n или crlf или какой-то другой "двойной стиль новой строки newline", тогда вам нужно будет указать 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));

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

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

Apache Commons имеет реализацию с использованием RandomAccessFile.

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

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

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

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

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

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

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

LineIterator lineIterator = FileUtils.lineIterator(newFile(filePath),"UTF-8");
String lastLine="";
while (lineIterator.hasNext()){
 lastLine=  lineIterator.nextLine();
}