Jak używać języka Java do odczytu z pliku, do którego jest aktywnie zapisywany?

99

Mam aplikację, która zapisuje informacje do pliku. Informacje te są wykorzystywane po wykonaniu do określenia wyniku / niepowodzenia / poprawności aplikacji. Chciałbym móc odczytać plik w trakcie jego zapisywania, aby móc przeprowadzać kontrole zaliczenia / niepowodzenia / poprawności w czasie rzeczywistym.

Zakładam, że jest to możliwe, ale jakie są problemy związane z używaniem Javy? Jeśli odczyt dogoni zapis, czy będzie po prostu czekał na kolejne zapisy, aż plik zostanie zamknięty, czy też odczyt zgłosi wyjątek w tym momencie? Jeśli to drugie, co mam wtedy zrobić?

Moja intuicja popycha mnie obecnie w stronę BufferedStreams. Czy to jest droga?

Anthony Cramp
źródło
1
hej, bo mam podobny scenariusz, który sobie wymyśliłem, jeśli znalazłeś lepsze rozwiązanie niż przyjęte?
Asaf David
Wiem, że to stare pytanie, ale czy dla dobra przyszłych czytelników możesz nieco rozszerzyć swój przypadek użycia? Nie mając więcej informacji, można się zastanawiać, czy być może rozwiązujesz niewłaściwy problem.
user359996
Przyjrzyj się używaniu Tailera z Apache Commons IO. Obsługuje większość przypadków skrajnych.
Joshua
2
Użyj bazy danych. Te scenariusze „czytania pliku podczas jego zapisywania” kończą się łzami.
Markiz Lorne,
@EJP - który DB polecacie? Zgaduję, że MySQL to dobry początek?
Kawa

Odpowiedzi:

39

Nie można uruchomić przykładu, FileChannel.read(ByteBuffer)ponieważ nie jest to odczyt blokujący. Czy jednak kod poniżej działał:

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

Oczywiście to samo działałoby jako licznik czasu zamiast wątku, ale zostawiam to programiście. Wciąż szukam lepszego sposobu, ale na razie to działa.

Aha, i zastrzegam to: używam 1.4.2. Tak, wiem, że wciąż jestem w epoce kamienia.

Joseph Gordon
źródło
1
Dzięki za dodanie tego ... czegoś, czego nigdy nie robiłem. Myślę, że odpowiedź Blade'a dotycząca blokowania pliku jest również dobra. Jednak wymaga Java 6 (chyba).
Anthony Cramp
@JosephGordon - Będziesz musiał przenieść się do wieku Drone w jeden z tych dni ;-)
TungstenX
12

Jeśli chcesz czytać plik w trakcie jego zapisywania i czytać tylko nową zawartość, to poniższe pomoże Ci to osiągnąć.

Aby uruchomić ten program, uruchomisz go z wiersza poleceń / okna terminala i przekażesz nazwę pliku do odczytania. Odczyta plik, chyba że zabijesz program.

java FileReader c: \ myfile.txt

Podczas wpisywania linii tekstu zapisz ją z notatnika, a zobaczysz tekst wydrukowany w konsoli.

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();
    }
}
tygrys wiosna
źródło
2
Czy nie istnieje możliwość, że między momentem odczytu pliku a czasem jego długości proces zewnętrzny mógłby dodać więcej danych do pliku. Jeśli tak, spowoduje to brak danych zapisanych w pliku w procesie odczytu.
JohnC,
1
Ten kod zużywa dużo procesora, ponieważ pętla nie zawiera wywołania thread.sleep. Bez dodania niewielkiej ilości opóźnienia ten kod ma tendencję do utrzymywania procesora bardzo obciążonego.
ChaitanyaBhatt
Oba powyższe komentarze są prawdziwe i dodatkowo takie BufferedReadertworzenie w każdym wywołaniu pętli jest bezcelowe. Gdyby był tworzony raz, pomijanie nie byłoby konieczne, ponieważ buforowany czytelnik czyta nowe wiersze, gdy tylko się pojawią.
Piotr
5

Całkowicie zgadzam się z odpowiedzią Joshuy , Tailer nadaje się do pracy w tej sytuacji. Oto przykład :

Zapisuje linię co 150 ms w pliku, podczas gdy ten sam plik odczytuje co 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
źródło
Twój link do Tailera jest uszkodzony.
Stealth Rabbi
2
@StealthRabbi Najlepszą rzeczą do zrobienia jest wyszukanie odpowiedniego linku i edycja za jego pomocą odpowiedzi.
igracia
3

Wydaje się, że odpowiedź brzmi „nie”… i „tak”. Wydaje się, że nie ma prawdziwego sposobu sprawdzenia, czy plik jest otwarty do zapisu w innej aplikacji. Tak więc czytanie z takiego pliku będzie kontynuowane do wyczerpania zawartości. Posłuchałem rady Mike'a i napisałem kod testowy:

Writer.java zapisuje ciąg do pliku, a następnie czeka, aż użytkownik naciśnie klawisz Enter, zanim zapisze kolejną linię do pliku. Chodzi o to, że można go uruchomić, a następnie czytelnik może zacząć zobaczyć, jak radzi sobie z plikiem „częściowym”. Czytelnik, którego napisałem, znajduje się w 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);
    }
}

Nie ma gwarancji, że ten kod jest najlepszymi praktykami.

Pozostawia to sugerowaną przez Mike'a opcję okresowego sprawdzania, czy z pliku są nowe dane do odczytania. Następnie wymaga to interwencji użytkownika w celu zamknięcia czytnika plików, gdy zostanie ustalone, że odczyt jest zakończony. Lub czytelnik musi być świadomy zawartości pliku i być w stanie określić i zakończyć zapis. Gdyby treść była w formacie XML, można by to zasygnalizować na końcu dokumentu.

Anthony Cramp
źródło
1

Nie Java jako taka, ale możesz napotkać problemy, w których zapisałeś coś do pliku, ale tak naprawdę nie został jeszcze zapisany - może znajdować się gdzieś w pamięci podręcznej, a odczyt z tego samego pliku może w rzeczywistości nie dać nowe informacje.

Wersja skrócona - użyj flush () lub innego odpowiedniego wywołania systemowego, aby upewnić się, że dane są faktycznie zapisywane w pliku.

Uwaga Nie mówię o pamięci podręcznej dysku na poziomie systemu operacyjnego - jeśli twoje dane dostaną się tutaj, powinny pojawić się w read () po tym momencie. Może się zdarzyć, że sam język buforuje zapisy, czekając, aż bufor się zapełni lub plik zostanie opróżniony / zamknięty.

Matthew Schinckel
źródło
1

Istnieje Open Source Java Graphic Tail, który to robi.

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
źródło
1

Nie można odczytać pliku, który jest otwierany z innego procesu za pomocą FileInputStream, FileReader lub RandomAccessFile.

Ale bezpośrednie użycie FileChannel zadziała:

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
źródło
0

Nigdy tego nie próbowałem, ale powinieneś napisać przypadek testowy, aby sprawdzić, czy czytanie ze strumienia po osiągnięciu końca będzie działać, niezależnie od tego, czy w pliku jest więcej danych.

Czy jest jakiś powód, dla którego nie możesz używać potokowego strumienia wejścia / wyjścia? Czy dane są zapisywane i odczytywane z tej samej aplikacji (jeśli tak, masz dane, po co czytać z pliku)?

W przeciwnym razie może czytać do końca pliku, a następnie monitorować zmiany i szukać miejsca, w którym skończyłeś, i kontynuować ... ale uważaj na warunki wyścigu.

Mike Stone
źródło