web-dev-qa-db-ja.com

テキストファイルの最後の行をすばやく読みますか?

Javaの[非常に大きな]ファイルからテキストの最後の行を読み取る最も速くて最も効率的な方法は何ですか?

58
Jake

C#の同様の質問 に対する私の答えを見てください。コードは非常に似ていますが、エンコーディングのサポートはJavaでは多少異なります。

基本的に、一般的に行うのはそれほど簡単なことではありません。 MSalterが指摘しているように、UTF-8では、_\r_または_\n_を簡単に見つけることができます。これらの文字のUTF-8表現はASCIIとまったく同じであり、これらのバイトはマルチバイト文字。

したがって、基本的には、(たとえば)2Kのバッファーを使用して、行の終端を確認しながら、逆方向に読み取ります(前に進む前に2Kにスキップし、次の2Kを読み取ります)。次に、ストリームの正確な場所にスキップして、一番上にInputStreamReaderを作成し、その上にBufferedReaderを作成します。次に、BufferedReader.readLine()を呼び出します。

19
Jon Skeet

以下は2つの関数です。1つはファイル全体をロードまたはステップスルーせずにファイルの最後の非空白行を返し、もう1つはステップスルーせずにファイルの最後のN行を返しますファイル全体:

末尾の処理は、ファイルの最後の文字に直接ズームし、文字ごとに後方に進み、改行が見つかるまで表示内容を記録します。改行が見つかると、ループから抜け出します。記録されたものを逆にし、それを文字列にスローして返します。 0xAは改行であり、0xDはキャリッジリターンです。

行末が\r\nまたはcrlfまたはその他の「二重改行スタイル改行」である場合、最後のn行を取得するためにn * 2行を指定する必要があります。ライン。

public String tail( File file ) {
    RandomAccessFile fileHandler = null;
    try {
        fileHandler = new RandomAccessFile( file, "r" );
        long fileLength = fileHandler.length() - 1;
        StringBuilder sb = new StringBuilder();

        for(long filePointer = fileLength; filePointer != -1; filePointer--){
            fileHandler.seek( filePointer );
            int readByte = fileHandler.readByte();

            if( readByte == 0xA ) {
                if( filePointer == fileLength ) {
                    continue;
                }
                break;

            } else if( readByte == 0xD ) {
                if( filePointer == fileLength - 1 ) {
                    continue;
                }
                break;
            }

            sb.append( ( char ) readByte );
        }

        String lastLine = sb.reverse().toString();
        return lastLine;
    } catch( Java.io.FileNotFoundException e ) {
        e.printStackTrace();
        return null;
    } catch( Java.io.IOException e ) {
        e.printStackTrace();
        return null;
    } finally {
        if (fileHandler != null )
            try {
                fileHandler.close();
            } catch (IOException e) {
                /* ignore */
            }
    }
}

しかし、おそらく最後の行は必要ないでしょう、最後のN行が必要なので、代わりにこれを使用してください:

public String tail2( File file, int lines) {
    Java.io.RandomAccessFile fileHandler = null;
    try {
        fileHandler = 
            new Java.io.RandomAccessFile( file, "r" );
        long fileLength = fileHandler.length() - 1;
        StringBuilder sb = new StringBuilder();
        int line = 0;

        for(long filePointer = fileLength; filePointer != -1; filePointer--){
            fileHandler.seek( filePointer );
            int readByte = fileHandler.readByte();

             if( readByte == 0xA ) {
                if (filePointer < fileLength) {
                    line = line + 1;
                }
            } else if( readByte == 0xD ) {
                if (filePointer < fileLength-1) {
                    line = line + 1;
                }
            }
            if (line >= lines) {
                break;
            }
            sb.append( ( char ) readByte );
        }

        String lastLine = sb.reverse().toString();
        return lastLine;
    } catch( Java.io.FileNotFoundException e ) {
        e.printStackTrace();
        return null;
    } catch( Java.io.IOException e ) {
        e.printStackTrace();
        return null;
    }
    finally {
        if (fileHandler != null )
            try {
                fileHandler.close();
            } catch (IOException e) {
            }
    }
}

上記のメソッドを次のように呼び出します:

File file = new File("D:\\stuff\\huge.log");
System.out.println(tail(file));
System.out.println(tail2(file, 10));

WarningUnicodeの西部では、このコードにより、この関数の出力が誤った結果になる可能性があります。たとえば、「Mary's」ではなく「Mary?s」です。 帽子、アクセント、漢字 などの文字は、アクセントが文字の後に修飾子として追加されるため、出力が間違っている可能性があります。複合文字を反転すると、反転時に文字のアイデンティティの性質が変わります。これを使用する予定のすべての言語で一連のテストを実行する必要があります。

このユニコード反転問題の詳細については、これをお読みください: http://msmvps.com/blogs/jon_skeet/archive/2009/11/02/omg-ponies-aka-humanity-epic-fail.aspx =

83
Eric Leschinski

Apache Commonsには、 RandomAccessFile を使用した実装があります。

ReversedLinesFileReader と呼ばれます。

30
jaco0646

FileReaderまたはFileInputStreamを使用しても機能しません。 FileChannel または RandomAccessFile を使用して、ファイルを最後から逆方向にループする必要があります。ジョンが言ったように、エンコーディングは問題になります。

4

私の知る限り、テキストファイルの最後の行を読む最も速い方法は、「org.Apache.commons.io」にあるFileUtils Apacheクラスを使用することです。 200万行のファイルがあり、このクラスを使用すると、最後の行を見つけるのに1秒もかかりませんでした。ここに私のコードがあります:

LineIterator lineIterator = FileUtils.lineIterator(newFile(filePath),"UTF-8");
String lastLine="";
while (lineIterator.hasNext()){
 lastLine=  lineIterator.nextLine();
}
2
arash nadali

最後の行を印刷するために以下のコードを簡単に変更できます。

最後の5行を印刷するためのMemoryMappedFile:

private static void printByMemoryMappedFile(File file) throws FileNotFoundException, IOException{
        FileInputStream fileInputStream=new FileInputStream(file);
        FileChannel channel=fileInputStream.getChannel();
        ByteBuffer buffer=channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size());
        buffer.position((int)channel.size());
        int count=0;
        StringBuilder builder=new StringBuilder();
        for(long i=channel.size()-1;i>=0;i--){
            char c=(char)buffer.get((int)i);
            builder.append(c);
            if(c=='\n'){
                if(count==5)break;
                count++;
                builder.reverse();
                System.out.println(builder.toString());
                builder=null;
                builder=new StringBuilder();
            }
        }
        channel.close();
    }

最後の5行を印刷するランダムアクセスファイル:

private static void printByRandomAcessFile(File file) throws FileNotFoundException, IOException{
        RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r");
        int lines = 0;
        StringBuilder builder = new StringBuilder();
        long length = file.length();
        length--;
        randomAccessFile.seek(length);
        for(long seek = length; seek >= 0; --seek){
            randomAccessFile.seek(seek);
            char c = (char)randomAccessFile.read();
            builder.append(c);
            if(c == '\n'){
                builder = builder.reverse();
                System.out.println(builder.toString());
                lines++;
                builder = null;
                builder = new StringBuilder();
                if (lines == 5){
                    break;
                }
            }

        }
    }
2
Trying
Path path = Paths.get(pathString);
      List<String> allLines = Files.readAllLines(path);
      return allLines.get(allLines.size()-1);
1
user2117229
try(BufferedReader reader = new BufferedReader(new FileReader(reqFile))) {

    String line = null;

    System.out.println("======================================");

    line = reader.readLine();       //Read Line ONE
    line = reader.readLine();       //Read Line TWO
    System.out.println("first line : " + line);

    //Length of one line if lines are of even length
    int len = line.length();       

    //skip to the end - 3 lines
    reader.skip((reqFile.length() - (len*3)));

    //Searched to the last line for the date I was looking for.

    while((line = reader.readLine()) != null){

        System.out.println("FROM LINE : " + line);
        String date = line.substring(0,line.indexOf(","));

        System.out.println("DATE : " + date);      //BAM!!!!!!!!!!!!!!
    }

    System.out.println(reqFile.getName() + " Read(" + reqFile.length()/(1000) + "KB)");
    System.out.println("======================================");
} catch (IOException x) {
    x.printStackTrace();
}
1
Ajai Singh

C#では、ストリームの位置を設定できるはずです:

From: http://bytes.com/groups/net-c/269090-streamreader-read-last-line-text-file

using(FileStream fs = File.OpenRead("c:\\file.dat"))
{
    using(StreamReader sr = new StreamReader(fs))
    {
        sr.BaseStream.Position = fs.Length - 4;
        if(sr.ReadToEnd() == "DONE")
            // match
    }
}
0
rball