[英]How to read the last n lines of a HUGE compressed file without decompressing the whole file to disk
[英]Java : Read last n lines of a HUGE file
我想读取一个非常大的文件的最后 n 行,而不是使用 Java 将整个文件读入任何缓冲区/内存区域。
我查看了 JDK API 和 Apache Commons I/O,但找不到适合此目的的。
我在想 UNIX 中 tail or 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++;
}
如果您使用RandomAccessFile
,您可以使用length
并seek
到达文件末尾附近的特定点,然后从那里向前读取。
如果您发现行数不足,请从该点备份并重试。 一旦你弄清楚最后N
行从哪里开始,你就可以找到那里并阅读和打印。
可以根据您的数据属性做出最初的最佳猜测假设。 例如,如果它是一个文本文件,则行长可能不会超过平均 132 行,因此,要获取最后五行,请在结束前 660 个字符开始。 然后,如果你错了,在 1320 再试一次(你甚至可以使用你从最后 660 个字符中学到的东西来调整它 - 例如:如果这 660 个字符只是三行,下一次尝试可能是 660 / 3 * 5,加上可能有点额外以防万一)。
如其他答案所述,RandomAccessFile 是一个很好的起点。 不过,有一个重要的警告。
如果您的文件未使用每个字符一个字节的编码进行编码,则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(...)
来计算行尾数。
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
和其他 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();
}
来自 apache commons 的CircularFifoBuffer 。 如何将 .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
允许搜索 (http://download.oracle.com/javase/1.4.2/docs/api/java/io/RandomAccessFile.html)。 File.length
方法将返回文件的大小。 问题是确定行数。 为此,您可以查找文件的末尾并向后阅读,直到找到正确的行数。
我有类似的问题,但我不理解其他解决方案。
我用过这个。 我希望那是简单的代码。
// 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();
}
}
public String readFromLast(File file, int howMany) throws IOException {
int numLinesRead = 0;
StringBuilder builder = new StringBuilder();
try (RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r")) {
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
long fileLength = file.length() - 1;
/*
* Set the pointer at the end of the file. If the file is empty, an IOException
* will be thrown
*/
randomAccessFile.seek(fileLength);
for (long pointer = fileLength; pointer >= 0; pointer--) {
randomAccessFile.seek(pointer);
byte b = (byte) randomAccessFile.read();
if (b == '\n') {
numLinesRead++;
// (Last line often terminated with a line separator)
if (numLinesRead == (howMany + 1))
break;
}
baos.write(b);
fileLength = fileLength - pointer;
}
/*
* Since line is read from the last so it is in reverse order. Use reverse
* method to make it ordered correctly
*/
byte[] a = baos.toByteArray();
int start = 0;
int mid = a.length / 2;
int end = a.length - 1;
while (start < mid) {
byte temp = a[end];
a[end] = a[start];
a[start] = temp;
start++;
end--;
}// End while
return new String(a).trim();
} // End inner try-with-resources
} // End outer try-with-resources
} // End method
这是我发现的最好的方法。 简单且非常快速且内存高效。
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();
}
我首先尝试了 RandomAccessFile,但向后读取文件很乏味,每次读取操作时都重新定位文件指针。 因此,我尝试了@Luca 解决方案,并在几分钟内将文件的最后几行作为字符串仅用了两行。
InputStream inputStream = Runtime.getRuntime().exec("tail " + path.toFile()).getInputStream();
String tail = new BufferedReader(new InputStreamReader(inputStream)).lines().collect(Collectors.joining(System.lineSeparator()));
代码只有2行
// Please specify correct Charset
ReversedLinesFileReader rlf = new ReversedLinesFileReader(file, StandardCharsets.UTF_8);
// read last 2 lines
System.out.println(rlf.toString(2));
Gradle:
implementation group: 'commons-io', name: 'commons-io', version: '2.11.0'
Maven:
<dependency>
<groupId>commons-io</groupId><artifactId>commons-io</artifactId><version>2.11.0</version>
</dependency>
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.