我想在Java中读取一个非常大的文件的最后n行,而不必将整个文件读入任何缓冲区或内存区域。
我查看了JDK API和Apache Commons I/O,并没有找到适合这个目的的方法。
我考虑使用UNIX中tail或less所使用的方法。我认为它们不会加载整个文件,然后显示文件的最后几行。在Java中应该也有类似的方法可以实现。
我想在Java中读取一个非常大的文件的最后n行,而不必将整个文件读入任何缓冲区或内存区域。
我查看了JDK API和Apache Commons I/O,并没有找到适合这个目的的方法。
我考虑使用UNIX中tail或less所使用的方法。我认为它们不会加载整个文件,然后显示文件的最后几行。在Java中应该也有类似的方法可以实现。
我发现使用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()
的输出没有被捕获。 - aapierceRandomAccessFile
,你可以使用 length
和 seek
来到达文件末尾附近的特定点,然后从那里向前读取。
如果发现行数不够,可以从该点回退并重试。一旦找出第N个最后一行的起始位置,就可以定位到那里并只读取和打印。
可以根据数据属性进行初步猜测。例如,如果是文本文件,则可能行长度不会超过平均值132,因此要获取最后五行,请在末尾前660个字符处开始。如果猜错了,再在1320处尝试(你甚至可以利用上一次的错误来调整-例如:如果那660个字符只是三行,下一次尝试可能是660 / 3 * 5,加上一些额外的预防措施)。
RandomAccessFile
的所有答案在处理100,000行文件时所需时间明显比Apache库更长。而且所有示例都返回字符串,而不是数组或列表。请参阅下面我的答案,它返回一个数组,而且没有使用Apache库。 - Adrian BartyczakRandomAccessFile是一个很好的起点,正如其他答案所述。然而,有一个重要的警告。
如果您的文件没有使用每个字符一个字节的编码方式进行编码,则readLine()
方法将无法工作。而readUTF()
在任何情况下都不会工作。(它读取一个由字符计数前导的字符串...)
相反,您需要确保以一种尊重编码字符边界的方式查找行尾标记。对于固定长度编码(例如UTF-16或UTF-32的变体),您需要从可被字符大小整除的字节位置开始提取字符。对于可变长度编码(例如UTF-8),您需要搜索必须是字符第一个字节的字节。
在UTF-8中,一个字符的第一个字节将是 0xxxxxxx
或 110xxxxx
或 1110xxxx
或 11110xxx
。 其他任何内容都是第二个/第三个字节或非法的UTF-8序列。请参见Unicode标准,版本5.2,第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
。 - Stephen CReversedLinesFileReader
可在 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;
result
变量按相反顺序保存了这些行。 - Markus Pscheidt我发现RandomAccessFile
和其他缓冲读取器类对我来说太慢了。没有什么比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系统。 - GrayCircularFifoBuffer 是来自Apache Commons的一个类。这个问题在如何将.txt文件的最后5行读入Java中也有相似的回答。
请注意,在Apache Commons Collections 4中,这个类似乎已经更名为CircularFifoQueue
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());
}
}
}
randomAccessFile.read();
返回的内容可以转换为有效的字符。例如,在UTF-8编码中,欧元符号将被编码为三个字节,即0xe2, 0x82, 0xac
。这意味着你需要读取字节,反转它们,然后再进行编码。我会尝试在下面发布一个重新编写的版本。 - g00sepublic static String[] getLastNLinesFromFile(String filePath, int numLines) throws IOException {
try (Stream<String> stream = Files.lines(Paths.get(filePath))) {
AtomicInteger offset = new AtomicInteger();
String[] lines = new String[numLines];
stream.forEach(line -> {
lines[offset.getAndIncrement() % numLines] = line;
});
List<String> list = IntStream.range(offset.get() < numLines ? 0 : offset.get() - numLines, offset.get())
.mapToObj(idx -> lines[idx % numLines]).collect(Collectors.toList());
return list.toArray(new String[0]);
}
}
RandomAccessFile
。 - dzim// 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) {
//
}
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();
}
}