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

StackOverflow https://stackoverflow.com/questions/4149

  •  08-06-2019
  •  | 
  •  

Pregunta

Tengo una aplicación que escribe información para archivar.Esta información se utiliza después de la ejecución para determinar la aprobación/fallo/corrección de la aplicación.Me gustaría poder leer el archivo mientras se escribe para poder realizar estas comprobaciones de aprobación/fallo/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á simplemente a que se escriban más hasta que se cierre el archivo, o la lectura generará una excepción en este punto?Si es esto último, ¿qué hago entonces?

Actualmente, mi intuición me está empujando hacia BufferedStreams.¿Es este el camino a seguir?

¿Fue útil?

Solución

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

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 lo dejo en manos del programador.Todavía estoy buscando una manera mejor, pero esto me funciona por ahora.

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

Otros consejos

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, deberá iniciarlo desde el símbolo del sistema/ventana de terminal y pasar el nombre del archivo para leer.Leerá el archivo a menos que cierre el programa.

java FileReader c:\miarchivo.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();
    }
}

También puedes echar un vistazo al canal Java para bloquear una parte de un archivo.

http://java.sun.com/javase/6/docs/api/java/nio/channels/FileChannel.html

Esta función del FileChannel podría ser un comienzo

lock(long position, long size, boolean shared) 

Una invocación de este método se bloqueará hasta que se pueda bloquear la región.

Estoy totalmente de acuerdo con La respuesta de Josué, sastre 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);
        }
    }
}

La respuesta parece ser no" ...y si".Parece que no hay una forma real de saber si un archivo está abierto para que otra aplicación lo escriba.Por lo tanto, la lectura de dicho archivo continuará 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 Intro antes de escribir otra línea en el archivo.La idea es que podría iniciarse y luego iniciar un lector para ver cómo se las arregla con el archivo "parcial".El lector que escribí está en Reader.java.

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

Lector.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 entonces la intervención del usuario para cerrar el lector de archivos cuando se determina que se completó la lectura.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 indicarlo.

No Java per se, pero es posible que se encuentre con problemas en los que haya escrito algo en un archivo, pero en realidad aún no se ha escrito; puede que esté en algún lugar de la memoria caché y es posible que leer del mismo archivo no le proporcione la nueva información.

Versión corta: use flush() o cualquiera que sea la llamada al sistema relevante para asegurarse de que sus datos realmente se escriban en el archivo.

Tenga en cuenta que no estoy hablando del caché de disco a nivel del sistema operativo; si sus datos ingresan aquí, deberían aparecer en 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.

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

Nunca lo he probado, pero deberías escribir un caso de prueba para ver si la lectura de una secuencia 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?¿Los datos se escriben y leen desde la misma aplicación (si es así, tiene los datos, por qué necesita leerlos del archivo)?

De lo contrario, tal vez lea hasta el final del archivo, luego controle los cambios y busque donde lo dejó y continúe...aunque tenga cuidado con las condiciones de carrera.

No puede leer un archivo que se abre desde otro proceso usando 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;
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top