快速读取文本文件的最后一行?

2022-08-31 14:28:05

在Java中,从[非常非常大]文件中读取最后一行文本的最快,最有效的方法是什么?


答案 1

下面是两个函数,一个返回文件的最后一个非空行而不加载或单步执行整个文件,另一个返回文件的最后 N 行而不单步执行整个文件:

tail 的作用是直接缩放到文件的最后一个字符,然后逐个字符地向后退一步,记录它所看到的内容,直到找到换行符。一旦它找到换行符,它就会中断循环。反转录制的内容并将其放入字符串中并返回。0xA是新行,0xD是回车符。

如果您的行尾是或或其他一些“双换行符样式换行符”,那么您必须指定n * 2行才能获得最后n行,因为它每行计数2行。\r\ncrlf

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 的狂野西部,此代码可能导致此函数的输出出错。例如,“玛丽的”而不是“玛丽的”。带有帽子,重音符号,中文字符等的字符可能会导致输出错误,因为重音符号是作为修饰符添加到字符之后的。反转复合字符会在反转时更改字符身份的性质。您将不得不对您计划使用它的所有语言进行全面的测试。

有关此 Unicode 反转问题的更多信息,请阅读以下内容:https://codeblog.jonskeet.uk/2009/11/02/omg-ponies-aka-humanity-epic-fail/


答案 2

Apache Commons有一个使用RandomAccessFile的实现。

它被称为ReversedLinesFileReader