Come si usa Java per leggere da un file su cui si sta attivamente scrivendo?


99

Ho un'applicazione che scrive informazioni su file. Queste informazioni vengono utilizzate dopo l'esecuzione per determinare l'esito positivo / negativo / la correttezza dell'applicazione. Mi piacerebbe poter leggere il file mentre viene scritto in modo da poter eseguire questi controlli di superamento / errore / correttezza in tempo reale.

Presumo che sia possibile farlo, ma quali sono i trucchi coinvolti quando si utilizza Java? Se la lettura raggiunge la scrittura, aspetterà solo più scritture fino alla chiusura del file, o la lettura genererà un'eccezione a questo punto? In quest'ultimo caso, cosa devo fare?

La mia intuizione attualmente mi sta spingendo verso BufferedStreams. È questa la strada da percorrere?


1
hey, dato che sto affrontando uno scenario simile stavo formulando se hai trovato una soluzione migliore di quella accettata?
Asaf David

So che questa è una vecchia domanda, ma per il bene dei lettori futuri, puoi espandere un po 'di più il tuo caso d'uso? Senza avere più informazioni, viene da chiedersi se forse stai risolvendo il problema sbagliato.
user359996

Cerca di utilizzare Tailer da Apache Commons IO. Gestisce la maggior parte dei casi limite.
Joshua,

2
Usa un database. Questi scenari di "leggere un file mentre viene scritto" finiscono in lacrime.
Marchese di Lorne

@EJP - quale DB mi consigliate? Immagino che MySQL sia un buon inizio?
Caffè

Risposte:


39

Impossibile far funzionare l'esempio utilizzando FileChannel.read(ByteBuffer)perché non è una lettura bloccante. Tuttavia ha fatto funzionare il codice seguente:

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

Ovviamente la stessa cosa funzionerebbe come timer invece che come thread, ma lo lascio al programmatore. Sto ancora cercando un modo migliore, ma per ora funziona.

Oh, e lo avverto con: sto usando 1.4.2. Sì, lo so che sono ancora nell'età della pietra.


1
Grazie per aver aggiunto questo ... qualcosa che non sono mai riuscito a fare. Penso che anche la risposta di Blade riguardo al blocco del file sia buona. Tuttavia, richiede Java 6 (credo).
Anthony Cramp

@JosephGordon - Uno di questi giorni dovrai passare al Drone Age ;-)
TungstenX

12

Se vuoi leggere un file mentre viene scritto e leggere solo il nuovo contenuto, seguire ti aiuterà a ottenere lo stesso risultato.

Per eseguire questo programma, lo avvierai dal prompt dei comandi / finestra del terminale e passerai il nome del file da leggere. Leggerà il file a meno che non si uccida il programma.

java FileReader c: \ myfile.txt

Mentre digiti una riga di testo salvalo dal blocco note e vedrai il testo stampato nella console.

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

2
Non c'è la possibilità che, tra il momento in cui il file viene letto e il tempo in cui viene presa la lunghezza del file, il processo esterno possa aggiungere più dati al file. In tal caso, nel processo di lettura mancano i dati scritti nel file.
JohnC

1
Questo codice consuma molta CPU perché il ciclo non contiene una chiamata thread.sleep. Senza aggiungere una piccola quantità di ritardo, questo codice tende a mantenere la CPU molto occupata.
ChaitanyaBhatt,

Entrambi i commenti sopra sono veri e inoltre questa BufferedReadercreazione in ogni chiamata di ciclo è così inutile. Se viene creato una volta, il salto non sarebbe necessario, poiché il lettore bufferizzato leggerebbe le nuove righe man mano che arrivano.
Piotr


5

Sono totalmente d'accordo con la risposta di Joshua , Tailer è adatto per il lavoro in questa situazione. Ecco un esempio:

Scrive una riga ogni 150 ms in un file, mentre legge lo stesso file ogni 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);
        }
    }
}

Il tuo collegamento a Tailer è interrotto.
Stealth Rabbi

2
@StealthRabbi La cosa migliore da fare quindi è cercare il collegamento corretto e modificare la risposta con esso.
igracia

3

La risposta sembra essere "no" ... e "sì". Non sembra esserci un vero modo per sapere se un file è aperto per la scrittura da un'altra applicazione. Quindi, la lettura da un file di questo tipo continuerà fino a quando il contenuto non sarà esaurito. Ho seguito il consiglio di Mike e ho scritto un codice di prova:

Writer.java scrive una stringa su file e quindi attende che l'utente prema Invio prima di scrivere un'altra riga su file. L'idea è che potrebbe essere avviato, quindi un lettore può essere avviato per vedere come affronta il file "parziale". Il lettore che ho scritto è in 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);
    }
}

Nessuna garanzia che questo codice sia la migliore pratica.

Questo lascia l'opzione suggerita da Mike di controllare periodicamente se ci sono nuovi dati da leggere dal file. Ciò richiede quindi l'intervento dell'utente per chiudere il lettore di file quando viene determinato che la lettura è stata completata. Oppure, il lettore deve essere informato del contenuto del file ed essere in grado di determinare la condizione di fine scrittura. Se il contenuto fosse XML, la fine del documento potrebbe essere utilizzata per segnalarlo.


1

Non Java di per sé, ma potresti incorrere in problemi in cui hai scritto qualcosa su un file, ma non è stato ancora effettivamente scritto - potrebbe essere in una cache da qualche parte e la lettura dallo stesso file potrebbe non darti le nuove informazioni.

Versione breve - usa flush () o qualunque altra chiamata di sistema rilevante sia per assicurarti che i tuoi dati siano effettivamente scritti nel file.

Nota non sto parlando della cache del disco a livello di sistema operativo: se i tuoi dati entrano qui, dovrebbero apparire in read () dopo questo punto. È possibile che la lingua stessa memorizzi nella cache le scritture, in attesa che un buffer si riempia o che il file venga scaricato / chiuso.


1

Ci sono una coda grafica Java Open Source che fa questo.

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

1

Non è possibile leggere un file aperto da un altro processo utilizzando FileInputStream, FileReader o RandomAccessFile.

Ma l'utilizzo diretto di FileChannel funzionerà:

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

0

Non l'ho mai provato, ma dovresti scrivere un test case per vedere se la lettura da uno stream dopo aver raggiunto la fine funzionerà, indipendentemente dal fatto che ci siano più dati scritti nel file.

C'è un motivo per cui non puoi utilizzare un flusso di input / output con pipe? I dati vengono scritti e letti dalla stessa applicazione (se sì, hai i dati, perché devi leggerli dal file)?

Altrimenti, puoi leggere fino alla fine del file, quindi monitorare le modifiche e cercare da dove eri rimasto e continuare ... anche se fai attenzione alle condizioni di gara.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.