[英]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.