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

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

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

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


person Anthony Cramp    schedule 06.08.2008    source источник
comment
эй, поскольку я сталкиваюсь с аналогичным сценарием, который я формулировал, если вы нашли лучшее решение, чем принятое?   -  person Asaf David    schedule 11.05.2010
comment
Я знаю, что это старый вопрос, но ради будущих читателей, не могли бы вы немного расширить свой вариант использования? Не имея дополнительной информации, можно задаться вопросом, возможно, вы решаете не ту проблему.   -  person user359996    schedule 17.11.2010
comment
Изучите использование Tailer из Apache Commons IO. Он обрабатывает большинство крайних случаев.   -  person Joshua    schedule 29.05.2012
comment
Используйте базу данных. Эти сценарии «чтение файла во время его записи» заканчиваются плачевно.   -  person user207421    schedule 12.01.2013
comment
@EJP - какую БД вы рекомендуете? Я предполагаю, что MySQL - хорошее начало?   -  person Caffeinated    schedule 24.09.2015


Ответы (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. Да, я знаю, что я все еще в каменном веке.

person Joseph Gordon    schedule 30.09.2008
comment
Спасибо за добавление этого ... то, что я никогда не делал. Я думаю, что ответ Blade о блокировке файла также хорош. Однако для этого требуется Java 6 (я думаю). - person Anthony Cramp; 02.10.2008
comment
@JosephGordon - На днях вам придется перейти на возраст дронов ;-) - person TungstenX; 30.11.2016

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

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

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();
    }
}
person tiger.spring    schedule 29.09.2015
comment
Не существует ли вероятность того, что между моментом чтения файла и моментом определения длины файла внешний процесс может добавить в файл дополнительные данные. Если это так, это приведет к тому, что в процессе чтения будут отсутствовать данные, записанные в файл. - person JohnC; 18.07.2016
comment
Этот код потребляет много ресурсов ЦП, потому что в цикле нет вызова thread.sleep. Без добавления небольшой задержки этот код имеет тенденцию сильно нагружать ЦП. - person ChaitanyaBhatt; 12.05.2017
comment
Оба приведенных выше комментария верны, и, кроме того, это создание BufferedReader в каждом вызове цикла настолько бессмысленно. Когда он создается один раз, пропуск не нужен, так как буферизованный ридер будет читать новые строки по мере их поступления. - person Piotr; 19.06.2020

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

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

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

lock(long position, long size, boolean shared) 

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

person Frederic Morin    schedule 01.09.2008

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

Он пишет в файл строку каждые 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);
        }
    }
}
person ToYonos    schedule 25.09.2015
comment
Ваша ссылка на Tailer не работает. - person Stealth Rabbi; 22.07.2016
comment
@StealthRabbi Тогда лучше всего найти правильную ссылку и отредактировать ответ с ее помощью. - person igracia; 15.11.2016

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

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, конец документа мог бы использоваться для обозначения этого.

person Anthony Cramp    schedule 07.08.2008

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

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();
}
person Rodrigo Menezes    schedule 02.03.2015

Вы не можете прочитать файл, открытый другим процессом с помощью 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;
}
person bebbo    schedule 03.01.2016

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

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

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

person Matthew Schinckel    schedule 07.08.2008

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

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

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

person Mike Stone    schedule 07.08.2008