【发布时间】:2011-05-06 12:29:26
【问题描述】:
我想读取一个非常大的文件的最后 n 行,而不是使用 Java 将整个文件读入任何缓冲区/内存区域。
我查看了 JDK API 和 Apache Commons I/O,但找不到适合此用途的。
我正在考虑在 UNIX 中使用 tail 或更少的方式。我认为他们不会加载整个文件然后显示文件的最后几行。在 Java 中也应该有类似的方法来做同样的事情。
【问题讨论】:
标签: java file-io large-files
我想读取一个非常大的文件的最后 n 行,而不是使用 Java 将整个文件读入任何缓冲区/内存区域。
我查看了 JDK API 和 Apache Commons I/O,但找不到适合此用途的。
我正在考虑在 UNIX 中使用 tail 或更少的方式。我认为他们不会加载整个文件然后显示文件的最后几行。在 Java 中也应该有类似的方法来做同样的事情。
【问题讨论】:
标签: java file-io large-files
package com.uday;
import java.io.File;
import java.io.RandomAccessFile;
public class TailN {
public static void main(String[] args) throws Exception {
long startTime = System.currentTimeMillis();
TailN tailN = new TailN();
File file = new File("/Users/udakkuma/Documents/workspace/uday_cancel_feature/TestOOPS/src/file.txt");
tailN.readFromLast(file);
System.out.println("Execution Time : " + (System.currentTimeMillis() - startTime));
}
public void readFromLast(File file) throws Exception {
int lines = 3;
int readLines = 0;
StringBuilder builder = new StringBuilder();
try (RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r")) {
long fileLength = file.length() - 1;
// Set the pointer at the last of the file
randomAccessFile.seek(fileLength);
for (long pointer = fileLength; pointer >= 0; pointer--) {
randomAccessFile.seek(pointer);
char c;
// read from the last, one char at the time
c = (char) randomAccessFile.read();
// break when end of the line
if (c == '\n') {
readLines++;
if (readLines == lines)
break;
}
builder.append(c);
fileLength = fileLength - pointer;
}
// Since line is read from the last so it is in reverse order. Use reverse
// method to make it correct order
builder.reverse();
System.out.println(builder.toString());
}
}
}
【讨论】:
ReversedLinesFileReader 可以在Apache Commons IO java 库中找到。
int n_lines = 1000;
ReversedLinesFileReader object = new ReversedLinesFileReader(new File(path));
String result="";
for(int i=0;i<n_lines;i++){
String line=object.readLine();
if(line==null)
break;
result+=line;
}
return result;
【讨论】:
RandomAccessFile 是一个很好的起点,正如其他答案所述。不过,有一个重要的警告。
如果您的文件未使用每个字符一个字节的编码,readLine() 方法将不适合您。而readUTF() 在任何情况下都不起作用。 (它读取一个以字符数开头的字符串...)
相反,您需要确保以尊重编码字符边界的方式查找行尾标记。对于固定长度编码(例如 UTF-16 或 UTF-32 的风格),您需要从可被字符大小(以字节为单位)整除的字节位置开始提取字符。对于可变长度编码(例如 UTF-8),您需要搜索一个 必须 是字符第一个字节的字节。
对于 UTF-8,字符的第一个字节将为 0xxxxxxx 或 110xxxxx 或 1110xxxx 或 11110xxx。其他任何内容要么是第二个/第三个字节,要么是非法的 UTF-8 序列。请参阅The Unicode Standard, Version 5.2, Chapter 3.9,表 3-7。这意味着,正如评论讨论所指出的,正确编码的 UTF-8 流中的任何 0x0A 和 0x0D 字节都将表示 LF 或 CR 字符。因此,如果我们可以假设不使用其他类型的 Unicode 行分隔符(0x2028、0x2029 和 0x0085),那么简单地计算 0x0A 和 0x0D 字节是一种有效的实现策略(对于 UTF-8)。你不能这么假设,那么代码会更复杂。
确定正确的字符边界后,您只需调用new String(...) 传递字节数组、偏移量、计数和编码,然后重复调用String.lastIndexOf(...) 来计算行尾数。
【讨论】:
0x0a 不是换行符的字符编码(例如 UTF-16),以及 2) 存在其他 Unicode 行分隔符码点的事实;例如0x2028、0x2029 和 0x0085
这是为此而工作的。
private static void printLastNLines(String filePath, int n) {
File file = new File(filePath);
StringBuilder builder = new StringBuilder();
try {
RandomAccessFile randomAccessFile = new RandomAccessFile(filePath, "r");
long pos = file.length() - 1;
randomAccessFile.seek(pos);
for (long i = pos - 1; i >= 0; i--) {
randomAccessFile.seek(i);
char c = (char) randomAccessFile.read();
if (c == '\n') {
n--;
if (n == 0) {
break;
}
}
builder.append(c);
}
builder.reverse();
System.out.println(builder.toString());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
【讨论】:
我发现使用apache commons-io api 中的ReversedLinesFileReader 是最简单的方法。
此方法将为您提供文件从底部到顶部的行,您可以指定 n_lines 值来指定行数。
import org.apache.commons.io.input.ReversedLinesFileReader;
File file = new File("D:\\file_name.xml");
int n_lines = 10;
int counter = 0;
ReversedLinesFileReader object = new ReversedLinesFileReader(file);
while(counter < n_lines) {
System.out.println(object.readLine());
counter++;
}
【讨论】:
readLine(),光标都会前进。所以这段代码实际上会错过每一行,因为while 语句中readLine() 的输出没有被捕获。
我有类似的问题,但我不明白其他解决方案。
我用过这个。我希望那是简单的代码。
// String filePathName = (direction and file name).
File f = new File(filePathName);
long fileLength = f.length(); // Take size of file [bites].
long fileLength_toRead = 0;
if (fileLength > 2000) {
// My file content is a table, I know one row has about e.g. 100 bites / characters.
// I used 1000 bites before file end to point where start read.
// If you don't know line length, use @paxdiablo advice.
fileLength_toRead = fileLength - 1000;
}
try (RandomAccessFile raf = new RandomAccessFile(filePathName, "r")) { // This row manage open and close file.
raf.seek(fileLength_toRead); // File will begin read at this bite.
String rowInFile = raf.readLine(); // First readed line usualy is not whole, I needn't it.
rowInFile = raf.readLine();
while (rowInFile != null) {
// Here I can readed lines (rowInFile) add to String[] array or ArriyList<String>.
// Later I can work with rows from array - last row is sometimes empty, etc.
rowInFile = raf.readLine();
}
}
catch (IOException e) {
//
}
【讨论】:
CircularFifoBuffer 来自 apache commons 。回答How to read last 5 lines of a .txt file into java的类似问题
请注意,在 Apache Commons Collections 4 中,此类似乎已重命名为 CircularFifoQueue
【讨论】:
我发现 RandomAccessFile 和其他 Buffer Reader 类对我来说太慢了。没有什么比tail -<#lines> 更快了。所以这对我来说是最好的解决方案。
public String getLastNLogLines(File file, int nLines) {
StringBuilder s = new StringBuilder();
try {
Process p = Runtime.getRuntime().exec("tail -"+nLines+" "+file);
java.io.BufferedReader input = new java.io.BufferedReader(new java.io.InputStreamReader(p.getInputStream()));
String line = null;
//Here we first read the next line into the variable
//line and then check for the EOF condition, which
//is the return value of null
while((line = input.readLine()) != null){
s.append(line+'\n');
}
} catch (java.io.IOException e) {
e.printStackTrace();
}
return s.toString();
}
【讨论】:
tail 本身可能是一个非常昂贵的提议,具体取决于您拥有多少内存。而且它也是 Unix 特定的。
这是我发现的最好的方法。简单且非常快速且内存高效。
public static void tail(File src, OutputStream out, int maxLines) throws FileNotFoundException, IOException {
BufferedReader reader = new BufferedReader(new FileReader(src));
String[] lines = new String[maxLines];
int lastNdx = 0;
for (String line=reader.readLine(); line != null; line=reader.readLine()) {
if (lastNdx == lines.length) {
lastNdx = 0;
}
lines[lastNdx++] = line;
}
OutputStreamWriter writer = new OutputStreamWriter(out);
for (int ndx=lastNdx; ndx != lastNdx-1; ndx++) {
if (ndx == lines.length) {
ndx = 0;
}
writer.write(lines[ndx]);
writer.write("\n");
}
writer.flush();
}
【讨论】:
maxLines 行,则第二个循环的条件不会终止。
如果您使用RandomAccessFile,您可以使用length 和seek 到达文件末尾附近的特定点,然后从那里向前读取。
如果您发现行数不足,请从该点备份并重试。一旦您确定了最后一行 Nth 的开始位置,您就可以找到那里并阅读并打印。
可以根据您的数据属性做出初步的最佳猜测假设。例如,如果它是一个文本文件,则行长可能不会超过平均 132 行,因此,要获取最后五行,请在结束前 660 个字符开始。然后,如果你错了,在 1320 再试一次(你甚至可以使用你从最后 660 个字符中学到的东西来调整它 - 例如:如果这 660 个字符只是三行,下一次尝试可能是 660 / 3 * 5,再加上一些额外的以防万一)。
【讨论】:
RandomAccessFile 允许搜索 (http://download.oracle.com/javase/1.4.2/docs/api/java/io/RandomAccessFile.html)。 File.length 方法将返回文件的大小。问题是确定行数。为此,您可以查找文件末尾并向后阅读,直到找到正确的行数。
【讨论】: