【发布时间】:2010-10-15 17:36:26
【问题描述】:
在 Java 中从 [非常、非常大] 文件中读取最后一行文本的最快和最有效的方法是什么?
【问题讨论】:
在 Java 中从 [非常、非常大] 文件中读取最后一行文本的最快和最有效的方法是什么?
【问题讨论】:
为了避免与恢复字符串(或 StringBuilder)相关的 Unicode 问题,如Eric Leschinski 优秀答案中所述,可以从文件末尾读取字节列表,将其恢复为字节数组,然后然后从字节数组创建字符串。
以下是对Eric Leschinski 答案代码的更改,以使用字节数组进行。代码更改在注释的代码行下方:
static 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();
List<Byte> sb = new ArrayList<>();
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.add( (char) readByte );
sb.add( (byte) readByte );
}
//String lastLine = sb.reverse().toString();
//Revert byte array and create String
byte[] bytes = new byte[sb.size()];
for (int i=0; i<sb.size(); i++) bytes[sb.size()-1-i] = sb.get(i);
String lastLine = new String(bytes);
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) {
}
}
}
【讨论】:
下面是两个函数,一个返回文件的最后一个非空行而不加载或单步执行整个文件,另一个返回文件的最后 N 行而不单步执行整个文件:
tail 的作用是直接缩放到文件的最后一个字符,然后逐个字符地后退,记录它看到的内容,直到找到换行符。一旦找到换行符,它就会跳出循环。反转记录的内容并将其放入字符串并返回。 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”。带有hats, accents, Chinese characters 等的字符可能会导致输出错误,因为重音符号作为修饰符添加在字符之后。反转复合字符会在反转时改变字符身份的性质。您必须对计划使用的所有语言进行全面测试。
有关此 Unicode 反转问题的更多信息,请阅读以下内容: https://codeblog.jonskeet.uk/2009/11/02/omg-ponies-aka-humanity-epic-fail/
【讨论】:
Path path = Paths.get(pathString);
List<String> allLines = Files.readAllLines(path);
return allLines.get(allLines.size()-1);
【讨论】:
efficient 只获取最后一行的方式..
据我所知,读取文本文件最后一行的最快方法是使用“org.apache.commons.io”中的 FileUtils Apache 类。我有一个 200 万行的文件,通过使用这个类,我用了不到一秒钟的时间找到了最后一行。这是我的代码:
LineIterator lineIterator = FileUtils.lineIterator(newFile(filePath),"UTF-8");
String lastLine="";
while (lineIterator.hasNext()){
lastLine= lineIterator.nextLine();
}
【讨论】:
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();
}
【讨论】:
Apache Commons 有一个使用 RandomAccessFile 的实现。
【讨论】:
readLine() 方法。
您可以轻松更改以下代码以打印最后一行。
用于打印最后 5 行的 MemoryMappedFile:
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;
}
}
}
}
【讨论】:
在 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
}
}
【讨论】:
看看我对similar question for C# 的回答。代码将非常相似,尽管 Java 中的编码支持有所不同。
总的来说,这并不是一件非常容易的事情。正如 MSalter 所指出的,UTF-8 确实可以很容易地发现 \r 或 \n,因为这些字符的 UTF-8 表示与 ASCII 相同,并且这些字节不会出现在多字节字符中。
所以基本上,取一个(比如说)2K 的缓冲区,然后逐步向后读取(跳到之前的 2K,读取下一个 2K)检查行终止。然后跳到流中正确的位置,在顶部创建一个InputStreamReader,并在其顶部创建一个BufferedReader。然后只需拨打BufferedReader.readLine()。
【讨论】:
使用 FileReader 或 FileInputStream 将不起作用 - 您必须使用 FileChannel 或 RandomAccessFile 从末尾向后循环文件。正如 Jon 所说,编码将是一个问题。
【讨论】: