Wie verwende ich Java, um aus einer Datei zu lesen, in die aktiv geschrieben wird?

99

Ich habe eine Anwendung, die Informationen in eine Datei schreibt. Diese Informationen werden nach der Ausführung verwendet, um das Bestehen / Nichtbestehen / die Richtigkeit der Anwendung zu bestimmen. Ich möchte in der Lage sein, die Datei während des Schreibens zu lesen, damit ich diese Pass / Failure / Korrektheitsprüfungen in Echtzeit durchführen kann.

Ich nehme an, dass dies möglich ist, aber was sind die Probleme bei der Verwendung von Java? Wenn der Lesevorgang den Schreibvorgang einholt, wartet er dann nur auf weitere Schreibvorgänge, bis die Datei geschlossen wird, oder löst der Lesevorgang an dieser Stelle eine Ausnahme aus? Wenn letzteres, was mache ich dann?

Meine Intuition treibt mich derzeit in Richtung BufferedStreams. Ist das der richtige Weg?

Anthony Cramp
quelle
1
Hey, da ich vor einem ähnlichen Szenario stehe, habe ich gesagt, ob Sie eine bessere Lösung als die akzeptierte gefunden haben?
Asaf David
Ich weiß, dass dies eine alte Frage ist, aber können Sie Ihren Anwendungsfall für zukünftige Leser etwas erweitern? Ohne weitere Informationen fragt man sich, ob Sie vielleicht das falsche Problem lösen.
user359996
Sehen Sie sich an, wie Sie den Tailer von Apache Commons IO verwenden. Es behandelt die meisten Randfälle.
Joshua
2
Verwenden Sie eine Datenbank. Diese Szenarien "Eine Datei lesen, während sie geschrieben wird" enden in Tränen.
Marquis von Lorne
@EJP - welche DB empfehlen Sie? Ich vermute, MySQL ist ein guter Anfang?
Kaffee

Antworten:

39

Das Beispiel konnte nicht zum Laufen gebracht werden, FileChannel.read(ByteBuffer)da es kein blockierendes Lesen ist. Hat jedoch den folgenden Code zum Laufen gebracht:

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

Natürlich würde das Gleiche als Timer anstelle eines Threads funktionieren, aber das überlasse ich dem Programmierer. Ich suche immer noch nach einem besseren Weg, aber das funktioniert für mich jetzt.

Oh, und ich werde dies mit folgenden Einschränkungen einschränken: Ich verwende 1.4.2. Ja, ich weiß, dass ich noch in der Steinzeit bin.

Joseph Gordon
quelle
1
Danke, dass du das hinzugefügt hast ... etwas, zu dem ich nie gekommen bin. Ich denke, Blades Antwort, die Datei zu sperren, ist auch gut. Es erfordert jedoch Java 6 (glaube ich).
Anthony Cramp
@ JosephGordon - Sie müssen eines Tages in die Drohne Alter ziehen ;-)
TungstenX
12

Wenn Sie eine Datei lesen möchten, während sie geschrieben wird, und nur den neuen Inhalt lesen möchten, können Sie die folgenden Schritte ausführen.

Um dieses Programm auszuführen, starten Sie es über die Eingabeaufforderung / das Terminalfenster und übergeben den Dateinamen zum Lesen. Die Datei wird gelesen, sofern Sie das Programm nicht beenden.

Java FileReader c: \ myfile.txt

Wenn Sie eine Textzeile eingeben, speichern Sie diese im Editor, und der Text wird in der Konsole gedruckt.

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();
    }
}
tiger.spring
quelle
2
Gibt es nicht die Möglichkeit, dass der externe Prozess zwischen dem Zeitpunkt des Lesens der Datei und dem Zeitpunkt der Dateilänge der Datei weitere Daten hinzufügen könnte? In diesem Fall fehlen beim Lesen Daten, die in die Datei geschrieben wurden.
JohnC
1
Dieser Code verbraucht viel CPU, da die Schleife keinen thread.sleep-Aufruf enthält. Ohne eine kleine Verzögerung hinzuzufügen, neigt dieser Code dazu, die CPU sehr beschäftigt zu halten.
ChaitanyaBhatt
Beide obigen Kommentare sind wahr und zusätzlich ist diese BufferedReaderErstellung in jedem Schleifenaufruf so sinnlos. Wenn es einmal erstellt wurde, wäre ein Überspringen nicht erforderlich, da der gepufferte Leser neue Zeilen lesen würde, sobald sie ankommen.
Piotr
5

Ich stimme Joshuas Antwort voll und ganz zu , Tailer ist in dieser Situation fit für den Job. Hier ist ein Beispiel :

Es schreibt alle 150 ms eine Zeile in eine Datei, während alle 2500 ms dieselbe Datei gelesen wird

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
quelle
Ihr Link zu Tailer ist unterbrochen.
Stealth Rabbi
2
@StealthRabbi Am besten suchen Sie dann nach dem richtigen Link und bearbeiten die Antwort damit.
igracia
3

Die Antwort scheint "nein" ... und "ja" zu sein. Es scheint keine wirkliche Möglichkeit zu geben, festzustellen, ob eine Datei zum Schreiben durch eine andere Anwendung geöffnet ist. Das Lesen aus einer solchen Datei wird also nur fortgesetzt, bis der Inhalt erschöpft ist. Ich nahm Mikes Rat an und schrieb einen Testcode:

Writer.java schreibt eine Zeichenfolge in die Datei und wartet darauf, dass der Benutzer die Eingabetaste drückt, bevor eine weitere Zeile in die Datei geschrieben wird. Die Idee ist, dass es gestartet werden könnte, dann kann ein Leser gestartet werden, um zu sehen, wie es mit der "partiellen" Datei umgeht. Der Reader, den ich geschrieben habe, ist 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);
    }
}

Keine Garantie, dass dieser Code eine bewährte Methode ist.

Dadurch bleibt die von Mike vorgeschlagene Option, regelmäßig zu überprüfen, ob neue Daten aus der Datei gelesen werden müssen. Dies erfordert dann ein Eingreifen des Benutzers, um den Dateireader zu schließen, wenn festgestellt wird, dass das Lesen abgeschlossen ist. Oder der Leser muss auf den Inhalt der Datei aufmerksam gemacht werden und in der Lage sein, den Schreibzustand zu bestimmen und zu beenden. Wenn der Inhalt XML wäre, könnte das Ende des Dokuments verwendet werden, um dies zu signalisieren.

Anthony Cramp
quelle
1

Nicht Java an sich, aber Sie können auf Probleme stoßen, bei denen Sie etwas in eine Datei geschrieben haben, aber es wurde noch nicht geschrieben - es befindet sich möglicherweise irgendwo in einem Cache, und das Lesen aus derselben Datei kann Ihnen möglicherweise nicht wirklich helfen die neuen Informationen.

Kurzversion - Verwenden Sie flush () oder einen anderen relevanten Systemaufruf, um sicherzustellen, dass Ihre Daten tatsächlich in die Datei geschrieben werden.

Hinweis Ich spreche nicht über den Festplatten-Cache auf Betriebssystemebene. Wenn Ihre Daten hier eingehen, sollten sie nach diesem Punkt in read () angezeigt werden. Es kann sein, dass die Sprache selbst Schreibvorgänge zwischenspeichert und wartet, bis ein Puffer voll ist oder die Datei geleert / geschlossen wird.

Matthew Schinckel
quelle
1

Es gibt einen Open Source Java Graphic Tail, der dies tut.

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
quelle
1

Sie können keine Datei lesen, die mit FileInputStream, FileReader oder RandomAccessFile aus einem anderen Prozess geöffnet wurde.

Die direkte Verwendung von FileChannel funktioniert jedoch:

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
quelle
0

Ich habe es noch nie versucht, aber Sie sollten einen Testfall schreiben, um zu sehen, ob das Lesen aus einem Stream nach dem Ende funktioniert, unabhängig davon, ob mehr Daten in die Datei geschrieben wurden.

Gibt es einen Grund, warum Sie keinen Pipeline-Eingabe- / Ausgabestream verwenden können? Werden die Daten aus derselben Anwendung geschrieben und gelesen (wenn ja, haben Sie die Daten, warum müssen Sie aus der Datei lesen)?

Andernfalls lesen Sie möglicherweise bis zum Ende der Datei, überwachen Sie dann auf Änderungen und suchen Sie, wo Sie aufgehört haben, und fahren Sie fort ... achten Sie jedoch auf die Rennbedingungen.

Mike Stone
quelle