Как использовать Java для чтения из файла, в который ведется активная запись?

У меня есть приложение, которое записывает информацию в файл. Эта информация используется после выполнения для определения успешности / неудачи / правильности приложения. Я хотел бы иметь возможность читать файл по мере его написания, чтобы я мог выполнять эти проверки прохождения / сбоя / правильности в режиме реального времени.

Я предполагаю, что это возможно, но какие проблемы возникают при использовании Java? Если чтение догонит запись, будет ли оно просто ждать новых записей, пока файл не будет закрыт, или чтение вызовет исключение в этот момент? Если второе, что мне тогда делать?

Моя интуиция в настоящее время подталкивает меня к BufferedStreams. Это путь?

Ответов (9)

Решение

Не удалось заставить пример работать, FileChannel.read(ByteBuffer) потому что это не блокирующее чтение. Однако заставил работать приведенный ниже код:

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

Конечно, то же самое будет работать как таймер вместо потока, но я оставляю это на усмотрение программиста. Я все еще ищу способ получше, но пока он мне подходит.

О, и я оговорюсь: я использую 1.4.2. Да, я знаю, что все еще живу в каменном веке.

Если вы хотите читать файл во время его записи и читать только новый контент, то следующее поможет вам добиться того же.

Чтобы запустить эту программу, вы запустите ее из командной строки / окна терминала и передадите имя файла для чтения. Он будет читать файл, если вы не убьете программу.

Java FileReader c: \ myfile.txt

По мере того, как вы вводите строку текста, сохраните ее из блокнота, и вы увидите текст, напечатанный в консоли.

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

Вы не можете прочитать файл, открытый из другого процесса с помощью FileInputStream, FileReader или RandomAccessFile.

Но использование FileChannel напрямую будет работать:

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

Я никогда не пробовал, но вам следует написать тестовый пример, чтобы увидеть, будет ли работать чтение из потока после того, как вы достигли конца, независимо от того, записано ли в файл больше данных.

Есть ли причина, по которой вы не можете использовать конвейерный поток ввода / вывода? Данные записываются и читаются из одного и того же приложения (если да, у вас есть данные, зачем вам читать из файла)?

В противном случае, возможно, можно прочитать до конца файла, затем следить за изменениями и искать с того места, где вы остановились, и продолжить ... хотя следите за условиями гонки.

Не Java как таковая, но вы можете столкнуться с проблемами, когда вы что-то записали в файл, но на самом деле это еще не было написано - это может быть где-то в кеше, и чтение из того же файла может фактически не дать вам новая информация.

Краткая версия - используйте flush () или любой другой соответствующий системный вызов, чтобы гарантировать, что ваши данные действительно записаны в файл.

Обратите внимание: я не говорю о дисковом кэше уровня ОС - если ваши данные попадают сюда, они должны появиться в read () после этого момента. Может случиться так, что сам язык кэширует записи, ожидая заполнения буфера или сброса / закрытия файла.

Ответ вроде бы «нет» ... и «да». Кажется, нет реального способа узнать, открыт ли файл для записи другим приложением. Таким образом, чтение из такого файла будет продолжаться до тех пор, пока содержимое не будет исчерпано. Я последовал совету Майка и написал тестовый код:

Writer.java записывает строку в файл, а затем ожидает, пока пользователь нажмет Enter, прежде чем записать другую строку в файл. Идея состоит в том, что он может быть запущен, тогда читатель может начать видеть, как он справляется с «частичным» файлом. Написанный мною ридер находится в 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);
    }
}

Нет гарантий, что этот код является наилучшей практикой.

Это оставляет вариант, предложенный Майком, периодически проверять, есть ли новые данные для чтения из файла. Затем требуется вмешательство пользователя, чтобы закрыть программу чтения файлов, когда будет определено, что чтение завершено. Или читатель должен быть осведомлен о содержимом файла и уметь определять условие окончания записи. Если бы контент был XML, конец документа мог бы сигнализировать об этом.

Вы также можете взглянуть на канал Java для блокировки части файла.

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

Эта функция FileChannel может быть началом

lock(long position, long size, boolean shared) 

Вызов этого метода будет заблокирован до тех пор, пока регион не будет заблокирован.

Для этого существует графический хвост Java с открытым исходным кодом.

https://answacode.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();
}

Я полностью согласен с ответом Джошуа , Тайлер подходит для этой работы в этой ситуации. Вот пример:

Он записывает строку в файл каждые 150 мс, а тот же файл читает каждые 2500 мс.

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