Leer un archivo línea por línea, en orden inverso


Tengo una aplicación java ee donde uso un servlet para imprimir un archivo de registro creado con log4j. Al leer archivos de registro, generalmente está buscando la última línea de registro y, por lo tanto, el servlet sería mucho más útil si imprimiera el archivo de registro en orden inverso. Mi código real es:

    response.setContentType("text");
    PrintWriter out = response.getWriter();
    try {
        FileReader logReader = new FileReader("logfile.log");
        try {
            BufferedReader buffer = new BufferedReader(logReader);
            for (String line = buffer.readLine(); line != null; line = buffer.readLine()) {
                out.println(line);
            }
        } finally {
            logReader.close();
        }
    } finally {
        out.close();
    }

Las implementaciones que he encontrado en Internet implican usar un StringBuffer y cargar todo el archivo antes de imprimir, ¿no hay una forma de código ligero de buscar hasta el final del archivo y leer el contenido hasta el inicio del archivo?

Author: eliocs, 2011-05-16

10 answers

[EDITAR]

A petición, estoy anteponiendo esta respuesta con el sentimiento de un comentario posterior: Si necesita este comportamiento con frecuencia, una solución "más apropiada" es probablemente mover sus registros de archivos de texto a tablas de base de datos con DBAppender (parte de log4j 2). A continuación, simplemente podría consultar las últimas entradas.

[/EDIT]

Probablemente abordaría esto de manera ligeramente diferente a las respuestas enumeradas.

(1) Crear una subclase de Writer que escribe la bytes codificados de cada carácter en orden inverso:

public class ReverseOutputStreamWriter extends Writer {
    private OutputStream out;
    private Charset encoding;
    public ReverseOutputStreamWriter(OutputStream out, Charset encoding) {
        this.out = out;
        this.encoding = encoding;
    }
    public void write(int ch) throws IOException {
        byte[] buffer = this.encoding.encode(String.valueOf(ch)).array();
        // write the bytes in reverse order to this.out
    }
    // other overloaded methods
}

(2) Crear una subclase de log4j WriterAppender cuyo método createWriter sería sobrescrito para crear una instancia de ReverseOutputStreamWriter.

(3) Crea una subclase de log4j Layout cuyo método format devuelve la cadena de registro en orden inverso de caracteres:

public class ReversePatternLayout extends PatternLayout {
    // constructors
    public String format(LoggingEvent event) {
        return new StringBuilder(super.format(event)).reverse().toString();
    }
}

(4) Modifique mi archivo de configuración de registro para enviar mensajes de registro a ambos el archivo de registro "normal" y un archivo de registro "inverso". El archivo de registro" inverso " contendría el mismo registro mensajes como el archivo de registro "normal", pero cada mensaje se escribiría al revés. (Tenga en cuenta que la codificación del archivo de registro "inverso" no necesariamente se ajustaría a UTF-8, ni siquiera a ninguna codificación de caracteres.)

(5) Crea una subclase de InputStream que envuelve una instancia de RandomAccessFile para leer los bytes de un archivo en orden inverso:

public class ReverseFileInputStream extends InputStream {
    private RandomAccessFile in;
    private byte[] buffer;
    // The index of the next byte to read.
    private int bufferIndex;
    public ReverseFileInputStream(File file) {
        this.in = new RandomAccessFile(File, "r");
        this.buffer = new byte[4096];
        this.bufferIndex = this.buffer.length;
        this.in.seek(file.length());
    }
    public void populateBuffer() throws IOException {
        // record the old position
        // seek to a new, previous position
        // read from the new position to the old position into the buffer
        // reverse the buffer
    }
    public int read() throws IOException {
        if (this.bufferIndex == this.buffer.length) {
            populateBuffer();
            if (this.bufferIndex == this.buffer.length) {
                return -1;
            }
        }
        return this.buffer[this.bufferIndex++];
    }
    // other overridden methods
}

Ahora, si quiero leer las entradas del archivo de registro "normal" en orden inverso, solo necesito crear una instancia de ReverseFileInputStream, dándole la archivo de registro" revere".

 11
Author: Nathan Ryan,
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
2011-05-16 21:13:47

Esta es una vieja pregunta. También quería hacer lo mismo y después de algunas búsquedas se encontró que hay una clase en apache commons-io para lograr esto:

org.apache.commons.io.input.ReversedLinesFileReader

 7
Author: Chathurika Sandarenu,
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-06-29 16:24:28

Creo que una buena opción para esto sería usar la clase RandomFileAccess. Hay un código de ejemplo para la retro-lectura usando esta clase en esta página. Leer bytes de esta manera es fácil, sin embargo leer cadenas podría ser un poco más difícil.

 4
Author: yms,
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
2011-05-15 21:37:47

Una alternativa más simple, porque dices que estás creando un servlet para hacer esto, es usar un LinkedList para contener las últimas líneas N (donde N podría ser un parámetro servlet). Cuando el tamaño de la lista excede N, se llama removeFirst().

Desde la perspectiva de la experiencia del usuario, esta es probablemente la mejor solución. Como usted nota, las líneas más recientes son las más importantes. No estar abrumado con información también es muy importante.

 2
Author: Anon,
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
2011-05-16 12:52:21

Si tiene prisa y desea la solución más simple sin preocuparse demasiado por el rendimiento, le daría una oportunidad de usar un proceso externo para hacer el trabajo sucio (dado que está ejecutando su aplicación en un servidor Un*x, como cualquier persona decente haría XD)

new BufferedReader(new InputStreamReader(Runtime.getRuntime().exec("tail yourlogfile.txt -n 50 | rev").getProcess().getInputStream()))
 2
Author: fortran,
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
2011-05-16 19:44:05

Buena pregunta. No tengo conocimiento de ninguna implementación común de esto. Tampoco es trivial hacerlo correctamente, así que ten cuidado con lo que elijas. Debe tratar con la codificación de conjuntos de caracteres y la detección de diferentes métodos de salto de línea. Aquí está la implementación que tengo hasta ahora que funciona con archivos codificados ASCII y UTF-8, incluido un caso de prueba para UTF-8. No funciona con archivos codificados UTF-16LE o UTF-16BE.

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.RandomAccessFile;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import junit.framework.TestCase;

public class ReverseLineReader {
    private static final int BUFFER_SIZE = 8192;

    private final FileChannel channel;
    private final String encoding;
    private long filePos;
    private ByteBuffer buf;
    private int bufPos;
    private byte lastLineBreak = '\n';
    private ByteArrayOutputStream baos = new ByteArrayOutputStream();

    public ReverseLineReader(File file, String encoding) throws IOException {
        RandomAccessFile raf = new RandomAccessFile(file, "r");
        channel = raf.getChannel();
        filePos = raf.length();
        this.encoding = encoding;
    }

    public String readLine() throws IOException {
        while (true) {
            if (bufPos < 0) {
                if (filePos == 0) {
                    if (baos == null) {
                        return null;
                    }
                    String line = bufToString();
                    baos = null;
                    return line;
                }

                long start = Math.max(filePos - BUFFER_SIZE, 0);
                long end = filePos;
                long len = end - start;

                buf = channel.map(FileChannel.MapMode.READ_ONLY, start, len);
                bufPos = (int) len;
                filePos = start;
            }

            while (bufPos-- > 0) {
                byte c = buf.get(bufPos);
                if (c == '\r' || c == '\n') {
                    if (c != lastLineBreak) {
                        lastLineBreak = c;
                        continue;
                    }
                    lastLineBreak = c;
                    return bufToString();
                }
                baos.write(c);
            }
        }
    }

    private String bufToString() throws UnsupportedEncodingException {
        if (baos.size() == 0) {
            return "";
        }

        byte[] bytes = baos.toByteArray();
        for (int i = 0; i < bytes.length / 2; i++) {
            byte t = bytes[i];
            bytes[i] = bytes[bytes.length - i - 1];
            bytes[bytes.length - i - 1] = t;
        }

        baos.reset();

        return new String(bytes, encoding);
    }

    public static void main(String[] args) throws IOException {
        File file = new File("my.log");
        ReverseLineReader reader = new ReverseLineReader(file, "UTF-8");
        String line;
        while ((line = reader.readLine()) != null) {
            System.out.println(line);
        }
    }

    public static class ReverseLineReaderTest extends TestCase {
        public void test() throws IOException {
            File file = new File("utf8test.log");
            String encoding = "UTF-8";

            FileInputStream fileIn = new FileInputStream(file);
            Reader fileReader = new InputStreamReader(fileIn, encoding);
            BufferedReader bufReader = new BufferedReader(fileReader);
            List<String> lines = new ArrayList<String>();
            String line;
            while ((line = bufReader.readLine()) != null) {
                lines.add(line);
            }
            Collections.reverse(lines);

            ReverseLineReader reader = new ReverseLineReader(file, encoding);
            int pos = 0;
            while ((line = reader.readLine()) != null) {
                assertEquals(lines.get(pos++), line);
            }

            assertEquals(lines.size(), pos);
        }
    }
}
 1
Author: WhiteFang34,
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
2011-05-16 03:40:21

Puede usar RandomAccessFile implementa esta función, como:

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;

import com.google.common.io.LineProcessor;
public class FileUtils {
/**
 * 反向读取文本文件(UTF8),文本文件分行是通过\r\n
 * 
 * @param <T>
 * @param file
 * @param step 反向寻找的步长
 * @param lineprocessor
 * @throws IOException
 */
public static <T> T backWardsRead(File file, int step,
        LineProcessor<T> lineprocessor) throws IOException {
    RandomAccessFile rf = new RandomAccessFile(file, "r");
    long fileLen = rf.length();
    long pos = fileLen - step;
    // 寻找倒序的第一行:\r
    while (true) {
        if (pos < 0) {
            // 处理第一行
            rf.seek(0);
            lineprocessor.processLine(rf.readLine());
            return lineprocessor.getResult();
        }
        rf.seek(pos);
        char c = (char) rf.readByte();
        while (c != '\r') {
            c = (char) rf.readByte();
        }
        rf.readByte();//read '\n'
        pos = rf.getFilePointer();
        if (!lineprocessor.processLine(rf.readLine())) {
            return lineprocessor.getResult();
        }
        pos -= step;
    }

  }

Uso:

       FileUtils.backWardsRead(new File("H:/usersfavs.csv"), 40,
            new LineProcessor<Void>() {
                                   //TODO  implements method
                                   .......
            });
 1
Author: user1536505,
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
2012-07-19 01:44:49

La solución más simple es leer el archivo en orden hacia adelante, usando un ArrayList<Long> para mantener el desplazamiento de bytes de cada registro de registro. Tendrá que usar algo como Jakarta Commons CountingInputStream para recuperar la posición de cada registro, y tendrá que organizar cuidadosamente sus búferes para asegurarse de que devuelve los valores adecuados:

FileInputStream fis = // .. logfile
BufferedInputStream bis = new BufferedInputStream(fis);
CountingInputStream cis = new CountingInputSteam(bis);
InputStreamReader isr = new InputStreamReader(cis, "UTF-8");

Y probablemente no podrá usar un BufferedReader, porque intentará leer por adelantado y perder la cuenta (pero leyendo un carácter a la vez no será un problema de rendimiento, porque se está almacenando en búfer más bajo en la pila).

Para escribir el archivo, itera la lista hacia atrás y usa un RandomAccessFile. Hay un pequeño truco: para decodificar correctamente los bytes (suponiendo una codificación multi-byte), tendrá que leer los bytes correspondientes a una entrada, y luego aplicar una decodificación a la misma. La lista, sin embargo, le dará la posición inicial y final de los bytes.

Un gran beneficio de este enfoque, versus simplemente al imprimir las líneas en orden inverso, no dañará los mensajes de registro multilínea (como las excepciones).

 0
Author: Anon,
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
2011-05-16 12:38:25
import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
 * Inside of C:\\temp\\vaquar.txt we have following content
 * vaquar khan is working into Citi He is good good programmer programmer trust me
 * @author [email protected]
 *
 */

public class ReadFileAndDisplayResultsinReverse {
    public static void main(String[] args) {
        try {
            // read data from file
            Object[] wordList = ReadFile();
            System.out.println("File data=" + wordList);
            //
            Set<String> uniquWordList = null;
            for (Object text : wordList) {
                System.out.println((String) text);
                List<String> tokens = Arrays.asList(text.toString().split("\\s+"));
                System.out.println("tokens" + tokens);
                uniquWordList = new HashSet<String>(tokens);
                // If multiple line then code into same loop
            }
            System.out.println("uniquWordList" + uniquWordList);

            Comparator<String> wordComp= new Comparator<String>() {

                @Override
                public int compare(String o1, String o2) {
                    if(o1==null && o2 ==null) return 0;
                    if(o1==null ) return o2.length()-0;
                    if(o2 ==null) return o1.length()-0;
                    //
                    return o2.length()-o1.length();
                }
            };
            List<String> fs=new ArrayList<String>(uniquWordList);
            Collections.sort(fs,wordComp);

            System.out.println("uniquWordList" + fs);

        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    static Object[] ReadFile() throws IOException {
        List<String> list = Files.readAllLines(new File("C:\\temp\\vaquar.txt").toPath(), Charset.defaultCharset());
        return list.toArray();
    }


}

Salida:

[Vaquar khan está trabajando en Citi Él es bueno buen programador programador confía en mí tokens [vaquar, khan, is, working, into, Citi, He, is, good, good, programmer, programmer, trust, me]

UniquWordList [trust, vaquar,programmer, is, good, into, khan, me, working, Citi, He]

UniquWordList [programmer, working,vaquar, trust, good, into, khan, Citi, is, me, He]

Si desea ordenar de A a Z, escriba un comparador más

 0
Author: vaquar khan,
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-03-02 09:21:31

Solución concisa usando Java 7 Autoclosables y Java 8 Streams:

try (Stream<String> logStream = Files.lines(Paths.get("C:\\logfile.log"))) {
   logStream
      .sorted(Comparator.reverseOrder())
      .limit(10) // last 10 lines
      .forEach(System.out::println);
}

Big drawback: solo funciona cuando las líneas están estrictamente en orden natural, como los archivos de registro con marcas de tiempo pero sin excepciones

 0
Author: Journeycorner,
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-08-26 23:17:04