¿Cómo utilizo Java para leer un archivo en el que se está escribiendo activamente?

99

Tengo una aplicación que escribe información en un archivo. Esta información se utiliza después de la ejecución para determinar si la aplicación se aprueba / falla / es correcta. Me gustaría poder leer el archivo a medida que se escribe para poder hacer estas comprobaciones de aprobación / falla / corrección en tiempo real.

Supongo que es posible hacer esto, pero ¿cuáles son los problemas involucrados al usar Java? Si la lectura se pone al día con la escritura, ¿esperará más escrituras hasta que se cierre el archivo, o la lectura arrojará una excepción en este punto? Si es lo último, ¿qué hago entonces?

Mi intuición me está empujando actualmente hacia BufferedStreams. ¿Es este el camino a seguir?

Anthony Calambre
fuente
1
Oye, como me enfrento a un escenario similar, estaba redactando si has encontrado una solución mejor que la aceptada.
Asaf David
Sé que esta es una pregunta antigua, pero por el bien de los futuros lectores, ¿puede ampliar un poco más su caso de uso? Sin tener más información, uno se pregunta si quizás esté resolviendo el problema equivocado.
user359996
Considere el uso de Tailer de Apache Commons IO. Maneja la mayoría de los casos extremos.
Joshua
2
Utilice una base de datos. Estos escenarios de 'leer un archivo mientras se escribe' terminan en lágrimas.
Marqués de Lorne
@EJP - ¿Qué base de datos me recomiendan? ¿Supongo que MySQL es un buen comienzo?
Café

Respuestas:

39

No se pudo hacer que el ejemplo funcionara FileChannel.read(ByteBuffer)porque no es una lectura de bloqueo. Sin embargo, el código siguiente funciona:

boolean running = true;
BufferedInputStream reader = new BufferedInputStream(new FileInputStream( "out.txt" ) );

public void run() {
    while( running ) {
        if( reader.available() > 0 ) {
            System.out.print( (char)reader.read() );
        }
        else {
            try {
                sleep( 500 );
            }
            catch( InterruptedException ex ) {
                running = false;
            }
        }
    }
}

Por supuesto, lo mismo funcionaría como un temporizador en lugar de un hilo, pero eso se lo dejo al programador. Todavía estoy buscando una mejor manera, pero esto funciona para mí por ahora.

Ah, y advertiré esto: estoy usando 1.4.2. Sí, sé que todavía estoy en la edad de piedra.

Joseph Gordon
fuente
1
Gracias por agregar esto ... algo que nunca pude hacer. Creo que la respuesta de Blade de bloquear el archivo también es buena. Sin embargo, requiere Java 6 (creo).
Anthony Cramp
@JosephGordon - Tendrás que mudarte a las edades de Drone uno de estos días ;-)
TungstenX
12

Si desea leer un archivo mientras se escribe y solo leer el contenido nuevo, lo siguiente le ayudará a lograr lo mismo.

Para ejecutar este programa, lo ejecutará desde el símbolo del sistema / ventana de terminal y pasará el nombre del archivo a leer. Leerá el archivo a menos que elimine el programa.

Java FileReader c: \ myfile.txt

A medida que escribe una línea de texto, guárdela desde el bloc de notas y verá el texto impreso en la consola.

public class FileReader {

    public static void main(String args[]) throws Exception {
        if(args.length>0){
            File file = new File(args[0]);
            System.out.println(file.getAbsolutePath());
            if(file.exists() && file.canRead()){
                long fileLength = file.length();
                readFile(file,0L);
                while(true){

                    if(fileLength<file.length()){
                        readFile(file,fileLength);
                        fileLength=file.length();
                    }
                }
            }
        }else{
            System.out.println("no file to read");
        }
    }

    public static void readFile(File file,Long fileLength) throws IOException {
        String line = null;

        BufferedReader in = new BufferedReader(new java.io.FileReader(file));
        in.skip(fileLength);
        while((line = in.readLine()) != null)
        {
            System.out.println(line);
        }
        in.close();
    }
}
tigre de primavera
fuente
2
¿No existe la posibilidad de que, entre el momento en que se lee el archivo y el momento en que se toma la longitud del archivo, el proceso externo podría agregar más datos al archivo? Si es así, esto provocaría que en el proceso de lectura falten datos escritos en el archivo.
JohnC
1
Este código consume mucha CPU porque el bucle no tiene una llamada thread.sleep. Sin agregar una pequeña cantidad de retraso, este código tiende a mantener a la CPU muy ocupada.
ChaitanyaBhatt
Ambos comentarios anteriores son ciertos y, además, esta BufferedReadercreación en cada llamada de bucle no tiene sentido. Al crearse una vez, no sería necesario omitir, ya que el lector en búfer leería las nuevas líneas a medida que lleguen.
Piotr
5

Estoy totalmente de acuerdo con la respuesta de Joshua , Tailer es apto para el trabajo en esta situación. Aquí hay un ejemplo :

Escribe una línea cada 150 ms en un archivo, mientras lee este mismo archivo cada 2500 ms

public class TailerTest
{
    public static void main(String[] args)
    {
        File f = new File("/tmp/test.txt");
        MyListener listener = new MyListener();
        Tailer.create(f, listener, 2500);

        try
        {
            FileOutputStream fos = new FileOutputStream(f);
            int i = 0;
            while (i < 200)
            {
                fos.write(("test" + ++i + "\n").getBytes());
                Thread.sleep(150);
            }
            fos.close();
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
    }

    private static class MyListener extends TailerListenerAdapter
    {
        @Override
        public void handle(String line)
        {
            System.out.println(line);
        }
    }
}
ToYonos
fuente
Su enlace a Tailer está roto.
Stealth Rabbi
2
@StealthRabbi Lo mejor que puede hacer es buscar el enlace correcto y editar la respuesta con él.
igracia
3

La respuesta parece ser "no" ... y "sí". Parece que no hay una forma real de saber si un archivo está abierto para escritura por otra aplicación. Por lo tanto, la lectura de un archivo de este tipo progresará hasta que se agote el contenido. Seguí el consejo de Mike y escribí un código de prueba:

Writer.java escribe una cadena en el archivo y luego espera a que el usuario presione enter antes de escribir otra línea en el archivo. La idea es que se pueda iniciar, luego se puede iniciar un lector para ver cómo maneja el archivo "parcial". El lector que escribí está en Reader.java.

Writer.java

public class Writer extends Object
{
    Writer () {

    }

    public static String[] strings = 
        {
            "Hello World", 
            "Goodbye World"
        };

    public static void main(String[] args) 
        throws java.io.IOException {

        java.io.PrintWriter pw =
            new java.io.PrintWriter(new java.io.FileOutputStream("out.txt"), true);

        for(String s : strings) {
            pw.println(s);
            System.in.read();
        }

        pw.close();
    }
}

Reader.java

public class Reader extends Object
{
    Reader () {

    }

    public static void main(String[] args) 
        throws Exception {

        java.io.FileInputStream in = new java.io.FileInputStream("out.txt");

        java.nio.channels.FileChannel fc = in.getChannel();
        java.nio.ByteBuffer bb = java.nio.ByteBuffer.allocate(10);

        while(fc.read(bb) >= 0) {
            bb.flip();
            while(bb.hasRemaining()) {
                System.out.println((char)bb.get());
            }
            bb.clear();
        }

        System.exit(0);
    }
}

No hay garantías de que este código sea la mejor práctica.

Esto deja la opción sugerida por Mike de verificar periódicamente si hay nuevos datos para leer del archivo. Esto requiere la intervención del usuario para cerrar el lector de archivos cuando se determina que la lectura está completa. O bien, el lector debe conocer el contenido del archivo y poder determinar la condición de fin de escritura. Si el contenido fuera XML, el final del documento podría usarse para señalar esto.

Anthony Calambre
fuente
1

No es Java per-se, pero puede tener problemas en los que haya escrito algo en un archivo, pero aún no se ha escrito en realidad; puede estar en un caché en algún lugar, y leer desde el mismo archivo puede que en realidad no le dé la nueva información.

Versión corta: use flush () o cualquier llamada al sistema relevante para asegurarse de que sus datos estén realmente escritos en el archivo.

Tenga en cuenta que no estoy hablando de la memoria caché del disco a nivel del sistema operativo; si sus datos entran aquí, deberían aparecer en un read () después de este punto. Puede ser que el propio lenguaje almacene en caché las escrituras, esperando hasta que se llene un búfer o se vacíe / cierre el archivo.

Matthew Schinckel
fuente
1

Hay una cola gráfica Java de código abierto que hace esto.

https://stackoverflow.com/a/559146/1255493

public void run() {
    try {
        while (_running) {
            Thread.sleep(_updateInterval);
            long len = _file.length();
            if (len < _filePointer) {
                // Log must have been jibbled or deleted.
                this.appendMessage("Log file was reset. Restarting logging from start of file.");
                _filePointer = len;
            }
            else if (len > _filePointer) {
                // File must have had something added to it!
                RandomAccessFile raf = new RandomAccessFile(_file, "r");
                raf.seek(_filePointer);
                String line = null;
                while ((line = raf.readLine()) != null) {
                    this.appendLine(line);
                }
                _filePointer = raf.getFilePointer();
                raf.close();
            }
        }
    }
    catch (Exception e) {
        this.appendMessage("Fatal error reading log file, log tailing has stopped.");
    }
    // dispose();
}
Rodrigo Menezes
fuente
1

No puede leer un archivo que se abre desde otro proceso utilizando FileInputStream, FileReader o RandomAccessFile.

Pero usar FileChannel directamente funcionará:

private static byte[] readSharedFile(File file) throws IOException {
    byte buffer[] = new byte[(int) file.length()];
    final FileChannel fc = FileChannel.open(file.toPath(), EnumSet.of(StandardOpenOption.READ));
    final ByteBuffer dst = ByteBuffer.wrap(buffer);
    fc.read(dst);
    fc.close();
    return buffer;
}
bebbo
fuente
0

Nunca lo he probado, pero debería escribir un caso de prueba para ver si la lectura de una transmisión después de haber llegado al final funcionará, independientemente de si hay más datos escritos en el archivo.

¿Hay alguna razón por la que no pueda utilizar un flujo de entrada / salida canalizado? ¿Se están escribiendo y leyendo los datos desde la misma aplicación (si es así, tiene los datos, por qué necesita leer desde el archivo)?

De lo contrario, puede leer hasta el final del archivo, luego monitorear los cambios y buscar donde lo dejó y continuar ... aunque tenga cuidado con las condiciones de la carrera.

Mike Stone
fuente