Leer rápidamente la última línea de un archivo de texto?


¿Cuál es la forma más rápida y eficiente de leer la última línea de texto de un archivo [muy, muy grande] en Java?

 55
Author: Gray, 2009-03-26

8 answers

Echa un vistazo a mi respuesta a una pregunta similar para C#. El código sería bastante similar, aunque el soporte de codificación es algo diferente en Java.

Básicamente no es una cosa terriblemente fácil de hacer en general. Como MSalter señala, UTF-8 hace que sea fácil detectar \r o \n ya que la representación UTF-8 de esos caracteres es igual que ASCII, y esos bytes no se producirán en carácter multi-byte.

Así que básicamente, tomar un búfer de (digamos) 2K, y lea progresivamente hacia atrás (salte a 2K antes de lo que estaba antes, lea los siguientes 2K) comprobando si hay una terminación de línea. Luego salta exactamente al lugar correcto en la secuencia, crea un InputStreamReader en la parte superior y un BufferedReader en la parte superior. Entonces solo llama BufferedReader.readLine().

 18
Author: Jon Skeet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 10:29:56

A continuación se muestran dos funciones, una que devuelve la última línea no en blanco de un archivo sin cargar o pasar por todo el archivo, y la otra que devuelve las últimas N líneas del archivo sin pasar por todo el archivo:

Lo que hace tail es acercar directamente al último carácter del archivo, luego retrocede, carácter por carácter, registrando lo que ve hasta que encuentra un salto de línea. Una vez que encuentra un salto de línea, se sale del bucle. Revierte lo que fue grabado y lo lanza en una cadena y retorna. 0xA es la nueva línea y 0xD es el retorno de carro.

Si sus terminaciones de línea son \r\n o crlf o alguna otra "nueva línea doble estilo nueva línea", entonces tendrá que especificar n*2 líneas para obtener las últimas n líneas porque cuenta 2 líneas por cada línea.

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 */
            }
    }
}

Pero probablemente no quieras la última línea, quieres las últimas N líneas, así que usa esto en su lugar:

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

Invoque los métodos anteriores como esto:

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

Advertencia En el salvaje oeste de unicode este código puede hacer que la salida de esta función salga mal. Por ejemplo " Mary?s "en lugar de "Mary's". Los caracteres con sombreros, acentos, caracteres chinos etc. pueden causar que la salida sea incorrecta porque los acentos se agregan como modificadores después del carácter. Invertir caracteres compuestos cambia la naturaleza de la identidad del carácter en la inversión. Usted tendrá que hacer la batería llena de pruebas en todos idiomas con los que planea usar esto.

Para obtener más información sobre este problema de reversión de unicode, lea esto: http://msmvps.com/blogs/jon_skeet/archive/2009/11/02/omg-ponies-aka-humanity-epic-fail.aspx

 81
Author: Eric Leschinski,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-03-10 13:29:42

Apache Commons tiene una implementación usando RandomAccessFile.

Se llama ReversedLinesFileReader.

 26
Author: jaco0646,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-07-15 14:06:18

Usar FileReader o FileInputStream no funcionará - tendrá que usar FileChannel o RandomAccessFile para recorrer el archivo hacia atrás desde el final. Sin embargo, las codificaciones serán un problema, como dijo Jon.

 3
Author: Michael Borgwardt,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-03-26 15:28:14

En C # , deberías poder establecer la posición del flujo:

De: 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
Author: rball,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-03-19 13:43:44

Puede cambiar fácilmente el siguiente código para imprimir la última línea.

MemoryMappedFile para imprimir las últimas 5 líneas:

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

RandomAccessFile para imprimir las últimas 5 líneas:

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

        }
    }
 0
Author: Trying,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-09-25 11:43:49
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();
}
 0
Author: Ajai Singh,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-04-03 07:49:52

Por lo que sé, la forma más rápida de leer la última línea de un archivo de texto es usar FileUtils Apache class que está en "org.apache.commons.io". Tengo un archivo de dos millones de líneas y al usar esta clase, me tomó menos de un segundo encontrar la última línea. Aquí está mi código:

LineIterator lineIterator = FileUtils.lineIterator(newFile(filePath),"UTF-8");
String lastLine="";
while (lineIterator.hasNext()){
 lastLine=  lineIterator.nextLine();
}
 0
Author: arash nadali,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-09-17 04:27:03