从Java中的[非常非常大的]文件中读取最后一行文本的最快捷,最有效的方法是什么?
下面是两个函数,一个返回文件的最后一个非空行而不加载或单步执行整个文件,另一个返回文件的最后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));
警告 在unicode的狂野西部,此代码可能导致此函数的输出错误.例如"Mary?s"而不是"Mary's".带有帽子,重音符号,汉字等的字符可能会导致输出错误,因为重音符号会在字符后添加为修饰符.反转复合字符会改变反转时字符身份的性质.您必须对计划使用此语言的所有语言进行全面的测试.
有关此unicode反转问题的更多信息,请阅读:http: //msmvps.com/blogs/jon_skeet/archive/2009/11/02/omg-ponies-aka-humanity-epic-fail.aspx
Apache Commons有一个使用RandomAccessFile的实现.
它叫做ReversedLinesFileReader.
看看我对C#的类似问题的答案.虽然Java中的编码支持有些不同,但代码非常相似.
基本上,一般来说,这不是一件非常容易的事情.正如MSalter指出的那样,UTF-8确实很容易发现,\r
或者\n
因为这些字符的UTF-8表示与ASCII相同,并且这些字节不会以多字节字符出现.
所以基本上,取一个(比方说)2K的缓冲区,然后逐步向后读(在你之前跳到2K,读下一个2K)检查线路终止.然后跳到流中正确的位置,InputStreamReader
在顶部创建一个,在顶部创建一个BufferedReader
.然后打电话BufferedReader.readLine()
.