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

99

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

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

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

Энтони Крэмп
источник
1
эй, поскольку я столкнулся с аналогичным сценарием, о котором я говорил, если вы нашли лучшее решение, чем принятое?
Асаф Давид,
Я знаю, что это старый вопрос, но, ради будущих читателей, можете ли вы немного расширить свой вариант использования? Не имея дополнительной информации, возникает вопрос, не решаете ли вы не ту проблему.
user359996
Изучите возможность использования Tailer от Apache Commons IO. Он обрабатывает большинство крайних случаев.
Джошуа
2
Используйте базу данных. Эти сценарии «чтение файла во время его записи» заканчиваются плачевно.
Marquis of Lorne
@EJP - какую БД вы рекомендуете? Полагаю, MySQL - хорошее начало?
Кофе

Ответы:

39

Не удалось заставить пример работать с использованием, 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. Да, я знаю, что все еще живу в каменном веке.

Джозеф Гордон
источник
1
Спасибо, что добавили это ... то, что я никогда не делал. Я думаю, что ответ Blade о блокировке файла также хорош. Однако для этого требуется Java 6 (я думаю).
Энтони Крэмп,
@JosephGordon - На днях тебе придется перейти на эпоху дронов ;-)
TungstenX
12

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

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

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();
    }
}
тигр. весна
источник
2
Разве не существует возможности, что между моментом чтения файла и моментом измерения длины файла внешний процесс может добавить в файл больше данных. Если это так, это приведет к тому, что в процессе чтения будут отсутствовать данные, записанные в файл.
JohnC
1
Этот код потребляет много ресурсов ЦП, потому что в цикле нет вызова thread.sleep. Без добавления небольшой задержки этот код имеет тенденцию сильно загружать процессор.
ChaitanyaBhatt
Оба приведенных выше комментария верны, и, кроме того, BufferedReaderсоздание каждого цикла при каждом вызове бессмысленно. Когда он создается один раз, в пропуске не будет необходимости, так как буферный читатель будет читать новые строки по мере их поступления.
Петр
5

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

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

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

lock(long position, long size, boolean shared) 

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

Фредерик Морен
источник
5

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

Он записывает строку в файл каждые 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);
        }
    }
}
ТоЙонос
источник
Ваша ссылка на Tailer не работает.
Stealth Rabbi
2
@StealthRabbi В этом случае лучше всего найти правильную ссылку и отредактировать с ее помощью ответ.
igracia
3

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

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

Энтони Крэмп
источник
1

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

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

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

Мэтью Шинкель
источник
1

Для этого существует графический хвост 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();
}
Родриго Менезеш
источник
1

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

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

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

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

Майк Стоун
источник