Java:读取HUGE文件的最后n行

时间:2010-11-08 06:17:29

标签: java file-io large-files

我想读取一个非常大的文件的最后n行,而不是使用Java将整个文件读入任何缓冲区/内存区域。

我查看了JDK API和Apache Commons I / O,但无法找到适合此目的的那个。

我在考虑在UNIX中使用tail或更少的方式。我不认为他们加载整个文件,然后显示该文件的最后几行。在Java中应该有类似的方法来做同样的事情。

11 个答案:

答案 0 :(得分:27)

如果您使用RandomAccessFile,则可以使用lengthseek到达文件末尾附近的特定点,然后从那里向前阅读。

如果您发现没有足够的线路,请从该点备份并重试。一旦你弄清楚N最后一行的开始位置,就可以在那里找到并只是阅读和打印。

可以根据您的数据属性进行初始最佳猜测假设。例如,如果它是一个文本文件,则行长度可能不会超过132的平均值,因此,要获得最后五行,请在结束前开始660个字符。然后,如果你错了,请在1320再试一次(你甚至可以使用你从最后660个字符中学到的东西来调整 - 例如:如果这660个字符只有三行,下一次尝试可能是660/3 * 5,加上可能有点额外的以防万一。)

答案 1 :(得分:27)

我发现使用ReversedLinesFileReader api中的apache commons-io是最简单的方法。 此方法将为您提供从文件的底部到顶部的行,您可以指定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++;
}

答案 2 :(得分:19)

RandomAccessFile是一个很好的起点,如其他答案所述。但是有一个重要的警告

如果您的文件没有使用每个字符一个字节的编码进行编码,则readLine()方法不适用于您。并且readUTF()在任何情况下都不起作用。 (它读取一个以字符数开头的字符串......)

相反,您需要确保以一种尊重编码字符边界的方式查找行尾标记。对于固定长度编码(例如UTF-16或UTF-32的风格),您需要从字节位置开始提取字符,这些字符位置可以按字节大小整除。对于可变长度编码(例如UTF-8),您需要搜索必须是字符的第一个字节的字节。

对于UTF-8,字符的第一个字节为0xxxxxxx110xxxxx1110xxxx11110xxx。其他任何东西都是第二个/第三个字节,或者是非法的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(...)来计算行尾。

答案 3 :(得分:4)

我发现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();
}

答案 4 :(得分:2)

来自apache commons的

CircularFifoBuffer。在How to read last 5 lines of a .txt file into java

回答类似的问题

请注意,在Apache Commons Collections 4中,此类似乎已重命名为CircularFifoQueue

答案 5 :(得分:1)

RandomAccessFile允许搜索(http://download.oracle.com/javase/1.4.2/docs/api/java/io/RandomAccessFile.html)。 File.length方法将返回文件的大小。问题是确定行数。为此,您可以寻找文件的末尾并向后阅读,直到您达到正确的行数。

答案 6 :(得分:1)

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;

答案 7 :(得分:1)

我有类似的问题,但我不理解另一种解决方案。

我用过这个。我希望这是简单的代码。

// 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) {
    //
}

答案 8 :(得分:1)

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());
        }

    }
}

答案 9 :(得分:0)

这是我发现的最佳方式。简单,快速,内存效率高。

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();
}

答案 10 :(得分:0)

这是工作所在。

    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();
    }
}