簡體   English   中英

Java:讀取一個巨大文件的最后 n 行

[英]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 ,您可以使用lengthseek到達文件末尾附近的特定點,然后從那里向前讀取。

如果您發現行數不足,請從該點備份並重試。 一旦你弄清楚最后N行從哪里開始,你就可以找到那里並閱讀和打印。

可以根據您的數據屬性做出最初的最佳猜測假設。 例如,如果它是一個文本文件,則行長可能不會超過平均 132 行,因此,要獲取最后五行,請在結束前 660 個字符開始。 然后,如果你錯了,在 1320 再試一次(你甚至可以使用你從最后 660 個字符中學到的東西來調整它 - 例如:如果這 660 個字符只是三行,下一次嘗試可能是 660 / 3 * 5,加上可能有點額外以防萬一)。

如其他答案所述,RandomAccessFile 是一個很好的起點。 不過,有一個重要的警告

如果您的文件未使用每個字符一個字節的編碼進行編碼,則readLine()方法將不適合您。 而且readUTF()在任何情況下都不起作用。 (它讀取一個以字符數開頭的字符串......)

相反,您需要確保以尊重編碼字符邊界的方式查找行尾標記。 對於固定長度編碼(例如 UTF-16 或 UTF-32 的風格),您需要從可被字符大小(以字節為單位)整除的字節位置開始提取字符。 對於可變長度編碼(例如 UTF-8),您需要搜索一個字節,該字節必須是字符的第一個字節。

在 UTF-8 的情況下,字符的第一個字節將是0xxxxxxx110xxxxx1110xxxx11110xxx 其他任何內容要么是第二個/第三個字節,要么是非法的 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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM