Làm cách nào để sử dụng Java để đọc từ một tệp đang được ghi vào?


99

Tôi có một ứng dụng ghi thông tin vào hồ sơ. Thông tin này được sử dụng sau khi thực thi để xác định tính đạt / sai / đúng của ứng dụng. Tôi muốn có thể đọc tệp khi nó đang được viết để tôi có thể thực hiện các kiểm tra đạt / không đạt / đúng này trong thời gian thực.

Tôi cho rằng có thể làm điều này, nhưng những gì liên quan đến gotcha khi sử dụng Java? Nếu việc đọc bắt kịp với việc ghi, nó sẽ chỉ đợi thêm các lần ghi cho đến khi tệp được đóng lại hay việc đọc sẽ đưa ra một ngoại lệ tại thời điểm này? Nếu sau này, tôi phải làm gì sau đó?

Trực giác của tôi hiện đang thúc đẩy tôi hướng tới BufferedStreams. Đây có phải là con đường để đi?


1
này, vì tôi đang phải đối mặt với một tình huống tương tự mà tôi đang diễn đạt liệu bạn đã tìm ra giải pháp tốt hơn giải pháp được chấp nhận chưa?
Asaf David

Tôi biết đây là một câu hỏi cũ, nhưng vì lợi ích của các độc giả trong tương lai, bạn có thể mở rộng trường hợp sử dụng của mình hơn một chút không? Nếu không có thêm thông tin, người ta tự hỏi liệu có lẽ bạn đang giải quyết vấn đề sai.
user359996,

Xem xét việc sử dụng Tailer từ Apache Commons IO. Nó xử lý hầu hết các trường hợp cạnh.
Joshua

2
Sử dụng cơ sở dữ liệu. Những tình huống 'đọc một tập tin trong khi nó được viết' kết thúc trong nước mắt.
Marquis of Lorne,

@EJP - bạn đề xuất DB nào? Tôi đoán MySQL là một khởi đầu tốt?
Cà phê

Câu trả lời:


39

Không thể làm cho ví dụ hoạt động bằng cách sử dụng FileChannel.read(ByteBuffer)vì nó không phải là đọc chặn. Tuy nhiên, mã dưới đây có hoạt động không:

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

Tất nhiên, điều tương tự sẽ hoạt động như một bộ đếm thời gian thay vì một luồng, nhưng tôi để điều đó cho lập trình viên. Tôi vẫn đang tìm một cách tốt hơn, nhưng điều này phù hợp với tôi bây giờ.

Ồ, và tôi sẽ báo trước điều này với: Tôi đang sử dụng 1.4.2. Vâng, tôi biết tôi vẫn đang ở trong thời kỳ đồ đá.


1
Cảm ơn vì đã thêm điều này ... điều mà tôi chưa bao giờ phải làm. Tôi nghĩ câu trả lời của Blade về việc khóa tệp cũng là một câu trả lời hay. Tuy nhiên, nó yêu cầu Java 6 (tôi nghĩ).
Anthony Cramp

@JosephGordon - Bạn sẽ phải chuyển sang tuổi Drone một trong những ngày này ;-)
TungstenX vào

12

Nếu bạn muốn đọc một tập tin trong khi nó đang được viết và chỉ đọc nội dung mới thì việc làm theo sẽ giúp bạn đạt được điều tương tự.

Để chạy chương trình này, bạn sẽ khởi chạy nó từ cửa sổ nhắc lệnh / cửa sổ đầu cuối và chuyển tên tệp để đọc. Nó sẽ đọc tệp trừ khi bạn giết chương trình.

java FileReader c: \ myfile.txt

Khi bạn nhập một dòng văn bản, hãy lưu nó từ notepad và bạn sẽ thấy văn bản được in trong bảng điều khiển.

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
Không có khả năng rằng, giữa thời gian tệp được đọc và thời gian độ dài tệp được thực hiện, quá trình bên ngoài có thể thêm nhiều dữ liệu hơn vào tệp. Nếu vậy, điều này sẽ dẫn đến quá trình đọc bị thiếu dữ liệu được ghi vào tệp.
JohnC

1
Đoạn mã này tiêu tốn rất nhiều CPU vì vòng lặp không có lệnh gọi thread.sleep trong đó. Nếu không thêm một lượng nhỏ độ trễ, mã này có xu hướng giữ cho CPU rất bận.
ChaitanyaBhatt

Cả hai nhận xét trên đều đúng và thêm vào đó, BufferedReaderviệc tạo ra này trong mỗi cuộc gọi vòng lặp là vô nghĩa. Với việc nó được tạo sau khi bỏ qua sẽ không cần thiết, vì trình đọc đệm sẽ đọc các dòng mới khi chúng đến.
Piotr


5

Tôi hoàn toàn đồng ý với câu trả lời của Joshua , Tailer phù hợp với công việc trong tình huống này. Đây là một ví dụ :

Nó ghi một dòng cứ sau 150 mili giây trong một tệp, trong khi đọc chính tệp này cứ sau 2500 mili giây

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

Liên kết của bạn đến Tailer bị hỏng.
Stealth Rabbi,

2
@StealthRabbi Điều tốt nhất cần làm sau đó là tìm kiếm liên kết chính xác và chỉnh sửa câu trả lời với nó.
igracia

3

Câu trả lời dường như là "không" ... và "có". Dường như không có cách thực sự nào để biết liệu một tệp có được mở để viết bởi ứng dụng khác hay không. Vì vậy, việc đọc từ một tệp như vậy sẽ chỉ tiến triển cho đến khi hết nội dung. Tôi đã nghe lời khuyên của Mike và viết một số mã thử nghiệm:

Writer.java ghi một chuỗi vào tệp và sau đó đợi người dùng nhấn enter trước khi ghi một dòng khác vào tệp. Ý tưởng là nó có thể được khởi động, sau đó người đọc có thể được bắt đầu để xem cách nó đối phó với tệp "một phần". Trình đọc mà tôi đã viết là trong 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);
    }
}

Không có gì đảm bảo rằng mã này là phương pháp hay nhất.

Điều này để lại tùy chọn được Mike đề xuất kiểm tra định kỳ nếu có dữ liệu mới được đọc từ tệp. Điều này sau đó yêu cầu sự can thiệp của người dùng để đóng trình đọc tệp khi xác định rằng việc đọc đã hoàn thành. Hoặc, người đọc cần được biết nội dung của tệp và có thể xác định và điều kiện kết thúc ghi. Nếu nội dung là XML, phần cuối của tài liệu có thể được sử dụng để báo hiệu điều này.


1

Không phải Java per-se, nhưng bạn có thể gặp phải sự cố trong đó bạn đã ghi một cái gì đó vào một tệp, nhưng nó chưa thực sự được ghi - nó có thể nằm trong bộ nhớ cache ở đâu đó và việc đọc từ cùng một tệp có thể không thực sự mang lại cho bạn thông tin mới.

Phiên bản ngắn - sử dụng flush () hoặc bất kỳ lệnh gọi hệ thống liên quan nào để đảm bảo rằng dữ liệu của bạn thực sự được ghi vào tệp.

Lưu ý Tôi không nói về bộ đệm đĩa cấp hệ điều hành - nếu dữ liệu của bạn vào đây, nó sẽ xuất hiện ở dạng read () sau thời điểm này. Có thể ngôn ngữ tự ghi vào bộ nhớ cache, đợi cho đến khi bộ đệm đầy hoặc tệp được xóa / đóng.


1

Có một Java Graphic Tail Nguồn mở thực hiện điều này.

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

Bạn không thể đọc tệp được mở từ quá trình khác bằng FileInputStream, FileReader hoặc RandomAccessFile.

Nhưng sử dụng FileChannel trực tiếp sẽ hoạt động:

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

Tôi chưa bao giờ thử nó, nhưng bạn nên viết một trường hợp thử nghiệm để xem liệu việc đọc từ một luồng sau khi bạn đọc xong có hoạt động hay không, bất kể có nhiều dữ liệu được ghi vào tệp hay không.

Có lý do gì khiến bạn không thể sử dụng luồng đầu vào / đầu ra có đường ống? Dữ liệu đang được ghi và đọc từ cùng một ứng dụng (nếu vậy, bạn có dữ liệu, tại sao bạn cần đọc từ tệp)?

Nếu không, có thể đọc cho đến cuối tệp, sau đó theo dõi các thay đổi và tìm kiếm nơi bạn đã dừng lại và tiếp tục ... mặc dù hãy chú ý đến điều kiện cuộc đua.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.