web-dev-qa-db-ja.com

Java:巨大ファイルの最後のn行を読み取る

Javaを使用してファイル全体をバッファ/メモリ領域に読み込まずに、非常に大きなファイルの最後のn行を読み込みたい。

JDK APIとApache Commons I/Oを調べたところ、この目的に適したものを見つけることができませんでした。

UNIXでtailがそれ以下にする方法を考えていました。ファイル全体をロードしてから、ファイルの最後の数行を表示するとは思わない。 Javaでも同じことを行う同様の方法があるはずです。

36
Gaurav Verma

RandomAccessFile を使用する場合、 length および seek を使用して取得できますファイルの終わり近くの特定のポイントに移動し、そこから前方に読み取ります。

行が足りないことがわかった場合は、その時点から戻ってやり直してください。 Nthの最後の行がどこから始まるかがわかったら、そこにシークし、読み取りと印刷を行うことができます。

データプロパティに基づいて、最初の推測を行うことができます。たとえば、テキストファイルの場合、行の長さが平均132を超えない可能性があるため、最後の5行を取得するには、終了前に660文字で開始します。次に、間違っていた場合は、1320で再試行します(最後の660文字から学んだことを使用して調整することもできます-例:これらの660文字が3行だけの場合、次の試行は660/3 * 5になります念のため、もう少し余分に追加してください)。

27
paxdiablo

ReversedLinesFileReader from Apache commons-io apiを使用して、最も簡単な方法を見つけました。このメソッドはファイルの一番下から一番上の行を提供し、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++;
}
27
akki_java

RandomAccessFileは、他の回答で説明されているように、開始するのに適した場所です。 重要な警告が1つあります。

ファイルが文字ごとに1バイトのエンコードでエンコードされていない場合、 readLine() メソッドは機能しません。 readUTF() はどのような状況でも機能しません。 (文字カウントが先行する文字列を読み取ります...)

代わりに、エンコードの文字境界を尊重する方法で行末マーカーを探すことを確認する必要があります。固定長エンコーディング(たとえば、UTF-16またはUTF-32のフレーバー)の場合、バイト単位の文字サイズで割り切れるバイト位置から始まる文字を抽出する必要があります。可変長エンコーディング(例:UTF-8)の場合、must文字の最初のバイトであるバイトを検索する必要があります。

UTF-8の場合、文字の最初のバイトは_0xxxxxxx_または_110xxxxx_または_1110xxxx_または_11110xxx_になります。それ以外は、2番目または3番目のバイト、または不正なUTF-8シーケンスです。 The Unicode Standard、Version 5.2、Chapter 3.9 、表3-7を参照してください。これは、コメントの議論が指摘するように、適切にエンコードされたUTF-8ストリーム内の0x0Aおよび0x0DバイトはLFまたはCR文字を表すことを意味します。したがって、0x0Aおよび0x0Dバイト他の種類のUnicode行区切り文字(0x2028、0x2029、および0x0085)が使用されていないと想定できる場合、有効な実装戦略(UTF-8)です。

適切な文字境界を特定したら、new String(...)を呼び出してバイト配列、オフセット、カウント、エンコードを渡し、String.lastIndexOf(...)を繰り返し呼び出して行末をカウントできます。

19
Stephen C

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
Luca

CircularFifoBuffer Apache commonsから。 。txtファイルの最後の5行をJavaに読み込む方法 の同様の質問から回答

Apache Commons Collections 4では、このクラスの名前が CircularFifoQueue に変更されているようです。

2
ruth542

ReversedLinesFileReaderApache 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;
1
Torsten Simon
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());
        }

    }
}
1
uday kumar

RandomAccessFileは、シークを可能にします(http://download.Oracle.com/javase/1.4.2/docs/api/Java/io/RandomAccessFile.html)。 File.lengthメソッドは、ファイルのサイズを返します。問題は、行数を決定することです。このために、ファイルの最後までシークし、適切な行数に達するまで逆読みできます。

1
Yann Ramin

私は同様の問題を抱えていましたが、別の解決策を理解していません。

これを使用しました。それがシンプルなコードであることを願っています。

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

これが、私が見つけた最善の方法です。シンプルで非常に高速で、メモリ効率が高い。

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

これがこのための働きです。

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