Bagaimana cara menggunakan Java untuk membaca dari file yang sedang aktif ditulis?

99

Saya memiliki aplikasi yang menulis informasi ke file. Informasi ini digunakan pasca-eksekusi untuk menentukan lulus / gagal / benar dari aplikasi. Saya ingin dapat membaca file seperti yang sedang ditulis sehingga saya dapat melakukan pemeriksaan kelulusan / kegagalan / ketepatan ini secara real time.

Saya berasumsi itu mungkin untuk melakukan ini, tetapi apa yang terlibat saat menggunakan Java? Jika pembacaan berhasil mencapai penulisan, apakah pembacaan akan menunggu lebih banyak penulisan hingga file ditutup, atau apakah pembacaan akan memunculkan pengecualian pada saat ini? Jika yang terakhir, apa yang harus saya lakukan?

Intuisi saya saat ini mendorong saya menuju BufferedStreams. Apakah ini cara untuk pergi?

Anthony Cramp
sumber
1
hei, karena saya menghadapi skenario serupa, saya berkata-kata jika Anda telah menemukan solusi yang lebih baik daripada yang diterima?
Asaf David
Saya tahu ini adalah pertanyaan lama, tetapi demi pembaca di masa mendatang, dapatkah Anda memperluas kasus penggunaan Anda sedikit lagi? Tanpa memiliki lebih banyak informasi, orang bertanya-tanya apakah Anda mungkin memecahkan masalah yang salah.
pengguna359996
Pertimbangkan untuk menggunakan Tailer dari Apache Commons IO. Ini menangani sebagian besar kasus tepi.
Joshua
2
Gunakan database. Skenario 'membaca file saat sedang ditulis' ini berakhir dengan air mata.
Marquis dari Lorne
@EJP - DB mana yang Anda rekomendasikan? Saya menduga MySQL adalah awal yang baik?
Kopi

Jawaban:

39

Tidak bisa mendapatkan contoh untuk digunakan FileChannel.read(ByteBuffer)karena ini bukan pembacaan yang memblokir. Namun apakah kode di bawah ini berfungsi:

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

Tentu saja hal yang sama akan bekerja sebagai pengatur waktu alih-alih utas, tetapi saya menyerahkannya kepada programmer. Saya masih mencari cara yang lebih baik, tetapi ini berhasil untuk saya untuk saat ini.

Oh, dan saya akan memperingati ini dengan: Saya menggunakan 1.4.2. Ya, saya tahu saya masih di zaman batu.

Joseph Gordon
sumber
1
Terima kasih telah menambahkan ini ... sesuatu yang belum pernah saya lakukan. Saya pikir jawaban Blade untuk mengunci file juga bagus. Namun, ini membutuhkan Java 6 (menurut saya).
Anthony Cramp
@JosephGordon - Anda harus pindah ke usia Drone pada suatu hari ;-)
TungstenX
12

Jika Anda ingin membaca file saat sedang ditulis dan hanya membaca konten baru, mengikuti akan membantu Anda mencapai hal yang sama.

Untuk menjalankan program ini, Anda akan meluncurkannya dari jendela command prompt / terminal dan meneruskan nama file untuk dibaca. Ini akan membaca file kecuali Anda mematikan program.

java FileReader c: \ myfile.txt

Saat Anda mengetik sebaris teks, simpan dari notepad dan Anda akan melihat teks tercetak di konsol.

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();
    }
}
tiger.spring
sumber
2
Tidakkah ada kemungkinan bahwa, antara waktu file dibaca dan waktu pengambilan panjang file, proses eksternal dapat menambahkan lebih banyak data ke file. Jika demikian, ini akan mengakibatkan proses membaca kehilangan data yang tertulis ke file.
JohnC
1
Kode ini menghabiskan banyak CPU karena loop tidak memiliki panggilan thread.sleep di dalamnya. Tanpa menambahkan sedikit penundaan kode ini cenderung membuat CPU sangat sibuk.
ChaitanyaBhatt
Kedua komentar di atas adalah benar dan tambahan BufferedReaderkreasi di setiap panggilan loop ini sangat tidak berguna. Dengan itu dibuat setelah melewatkan tidak diperlukan, karena pembaca buffer akan membaca baris baru saat mereka tiba.
Piotr
5

Saya sangat setuju dengan tanggapan Joshua , Tailer cocok untuk pekerjaan dalam situasi ini. Berikut ini contohnya:

Ia menulis satu baris setiap 150 ms dalam sebuah file, sementara membaca file yang sama ini setiap 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
sumber
Tautan Anda ke Tailer rusak.
Stealth Rabbi
2
@StealthRabbi Hal terbaik yang harus dilakukan selanjutnya adalah mencari tautan yang benar, dan mengedit jawabannya dengannya.
igracia
3

Jawabannya sepertinya "tidak" ... dan "ya". Sepertinya tidak ada cara nyata untuk mengetahui apakah sebuah file terbuka untuk ditulis oleh aplikasi lain. Jadi, membaca dari file seperti itu hanya akan berlanjut sampai konten habis. Saya mengikuti saran Mike dan menulis beberapa kode tes:

Writer.java menulis string ke file dan kemudian menunggu pengguna menekan enter sebelum menulis baris lain ke file. Idenya adalah bahwa itu bisa dimulai, kemudian pembaca dapat mulai melihat bagaimana cara mengatasi file "parsial". Pembaca yang saya tulis ada di 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);
    }
}

Tidak ada jaminan bahwa kode ini adalah praktik terbaik.

Ini meninggalkan opsi yang disarankan oleh Mike untuk memeriksa secara berkala apakah ada data baru untuk dibaca dari file tersebut. Hal ini kemudian memerlukan intervensi pengguna untuk menutup pembaca file ketika ditentukan bahwa pembacaan telah selesai. Atau, pembaca perlu diberi tahu tentang konten file dan dapat menentukan serta kondisi akhir penulisan. Jika isinya XML, akhir dokumen dapat digunakan untuk menandakan ini.

Anthony Cramp
sumber
1

Bukan Java itu sendiri, tetapi Anda mungkin mengalami masalah di mana Anda telah menulis sesuatu ke file, tetapi belum benar-benar ditulis - mungkin ada di cache di suatu tempat, dan membaca dari file yang sama mungkin tidak benar-benar memberi Anda informasi baru.

Versi singkat - gunakan flush () atau apa pun panggilan sistem yang relevan untuk memastikan bahwa data Anda benar-benar ditulis ke file.

Catatan Saya tidak berbicara tentang cache disk tingkat OS - jika data Anda masuk ke sini, itu akan muncul di read () setelah titik ini. Mungkin bahasa itu sendiri menyimpan cache penulisan, menunggu hingga buffer terisi atau file dibilas / ditutup.

Matthew Schinckel
sumber
1

Ada Open Source Java Graphic Tail yang melakukan ini.

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
sumber
1

Anda tidak dapat membaca file yang dibuka dari proses lain menggunakan FileInputStream, FileReader atau RandomAccessFile.

Tetapi menggunakan FileChannel secara langsung akan berfungsi:

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
sumber
0

Saya belum pernah mencobanya, tetapi Anda harus menulis kasus uji untuk melihat apakah membaca dari aliran setelah Anda mencapai akhir akan berfungsi, terlepas dari apakah ada lebih banyak data yang ditulis ke file.

Apakah ada alasan mengapa Anda tidak dapat menggunakan aliran input / output yang disalurkan? Apakah data ditulis dan dibaca dari aplikasi yang sama (jika demikian, Anda memiliki datanya, mengapa Anda perlu membaca dari file)?

Jika tidak, mungkin membaca sampai akhir file, kemudian memantau perubahan dan mencari di mana Anda tinggalkan dan melanjutkan ... meskipun perhatikan kondisi balapan.

Mike Stone
sumber