Як використовувати Java для читання з файлу, в який активно записується?


99

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

Я припускаю, що це можна зробити, але чим займаються ґетчі при використанні Java? Якщо читання наздожене письмове запитання, чи буде він просто чекати, коли запишеться більше, доки файл не закриється, або читання в цьому моменті викине виняток? Якщо останнє, що мені робити тоді?

Наразі моя інтуїція підштовхує мене до BufferedStreams. Це шлях?


1
Гей, як я стикаюся з подібним сценарієм, я формулював, якщо ви знайшли краще рішення, ніж прийняте?
Асаф Давид,

Я знаю, що це давнє запитання, але заради майбутніх читачів ви можете трохи більше розширити питання щодо використання? Не маючи більше інформації, можна задатися питанням, чи можливо ви вирішили неправильну проблему.
користувач359996

Погляньте на використання Tailer від IOche Commons IO. Він обробляє більшість крайових випадків.
Джошуа

2
Використовуйте базу даних. Ці сценарії "читати файл, поки він пишеться" закінчуються сльозами.
Маркіз Лорн

@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
Дякую, що додав це ... те, чого я ніколи не міг зробити. Я думаю, що відповідь Блейда на блокування файлу також є доброю. Однак для цього потрібна 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
Цей код споживає багато процесора, тому що в циклі немає потоку. Без додавання невеликої кількості затримки цей код має тенденцію тримати процесор дуже зайнятим.
ЧайтаньяБатт

Обидва коментарі вище є правдивими, і крім того, це 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();
    }
}

Читач.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

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

Чи є причина, по якій ви не можете використовувати конвеєрний вхідний / вихідний потік? Чи дані записуються та читаються з одного додатка (якщо так, то у вас є дані, навіщо вам потрібно читати з файлу)?

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.