Como uso o Java para ler um arquivo que está sendo gravado ativamente?

99

Eu tenho um aplicativo que grava informações em um arquivo. Essas informações são usadas pós-execução para determinar a aprovação / falha / correção do aplicativo. Eu gostaria de poder ler o arquivo enquanto ele está sendo escrito para que eu possa fazer essas verificações de aprovação / falha / exatidão em tempo real.

Presumo que seja possível fazer isso, mas quais são as pegadinhas envolvidas ao usar Java? Se a leitura alcançar a gravação, ela apenas aguardará por mais gravações até que o arquivo seja fechado ou a leitura lançará uma exceção neste ponto? Se for o último, o que eu faço então?

Minha intuição está atualmente me empurrando para BufferedStreams. É este o caminho a percorrer?

Anthony Cramp
fonte
1
Ei, como estou enfrentando um cenário semelhante, eu estava formulando se você encontrou uma solução melhor do que a aceita?
Asaf David de
Eu sei que esta é uma pergunta antiga, mas para o bem dos futuros leitores, você pode expandir seu caso de uso um pouco mais? Sem ter mais informações, pode-se perguntar se você talvez esteja resolvendo o problema errado.
user359996
Procure usar o Tailer do Apache Commons IO. Ele lida com a maioria dos casos extremos.
Josué
2
Use um banco de dados. Esses cenários de 'ler um arquivo enquanto ele está sendo escrito' terminam em lágrimas.
Marquês de Lorne
@EJP - qual DB você recomenda? Estou supondo que o MySQL é um bom começo?
Café de

Respostas:

39

Não foi possível fazer o exemplo funcionar FileChannel.read(ByteBuffer)porque não é uma leitura de bloqueio. No entanto, conseguiu que o código abaixo funcionasse:

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

Claro que a mesma coisa funcionaria como um temporizador em vez de um thread, mas deixo isso para o programador. Ainda estou procurando uma maneira melhor, mas isso funciona para mim por enquanto.

Ah, e vou advertir isso com: Estou usando 1.4.2. Sim, eu sei que ainda estou na idade da pedra.

Joseph Gordon
fonte
1
Obrigado por adicionar isso ... algo que eu nunca tive tempo de fazer. Acho que a resposta de Blade de bloquear o arquivo também é boa. No entanto, requer Java 6 (eu acho).
Anthony Cramp,
@JosephGordon - Você terá que se mudar para o Drone um dia desses ;-)
TungstenX
12

Se você deseja ler um arquivo enquanto ele está sendo escrito e apenas ler o novo conteúdo, o seguinte o ajudará a conseguir o mesmo.

Para executar este programa, você irá iniciá-lo a partir do prompt de comando / janela do terminal e passar o nome do arquivo para leitura. Ele lerá o arquivo, a menos que você mate o programa.

java FileReader c: \ myfile.txt

Conforme você digita uma linha de texto, salve-a do bloco de notas e você verá o texto impresso no console.

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
fonte
2
Não existe a possibilidade de que, entre o momento em que o arquivo é lido e o momento em que seu comprimento é obtido, o processo externo possa adicionar mais dados ao arquivo? Nesse caso, isso resultaria na perda de dados do processo de leitura gravados no arquivo.
JohnC
1
Este código consome muita CPU porque o loop não contém uma chamada thread.sleep. Sem adicionar uma pequena quantidade de atraso, esse código tende a manter a CPU muito ocupada.
ChaitanyaBhatt
Ambos os comentários acima são verdadeiros e, além disso, esta BufferedReadercriação em cada chamada de loop é tão inútil. Com ele sendo criado uma vez, o salto não seria necessário, pois o leitor em buffer leria as novas linhas à medida que chegassem.
Piotr
5

Eu concordo totalmente com a resposta de Joshua , Tailer está apto para o trabalho nesta situação. Aqui está um exemplo :

Ele grava uma linha a cada 150 ms em um arquivo, enquanto lê esse mesmo arquivo a cada 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
fonte
Seu link para Tailer está quebrado.
Stealth Rabino
2
@StealthRabbi A melhor coisa a fazer então é pesquisar o link correto e editar a resposta com ele.
igracia de
3

A resposta parece ser "não" ... e "sim". Parece não haver uma maneira real de saber se um arquivo está aberto para gravação por outro aplicativo. Portanto, a leitura desse arquivo irá apenas progredir até que o conteúdo se esgote. Segui o conselho de Mike e escrevi alguns códigos de teste:

Writer.java grava uma string no arquivo e, em seguida, espera que o usuário pressione Enter antes de gravar outra linha no arquivo. A ideia é que ele possa ser iniciado, então um leitor pode ser iniciado para ver como ele lida com o arquivo "parcial". O leitor que escrevi está em 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);
    }
}

Não há garantias de que este código seja a prática recomendada.

Isso deixa a opção sugerida por Mike de verificar periodicamente se há novos dados a serem lidos do arquivo. Isso exige a intervenção do usuário para fechar o leitor de arquivos quando for determinado que a leitura foi concluída. Ou, o leitor precisa estar ciente do conteúdo do arquivo e ser capaz de determinar a condição de fim de gravação. Se o conteúdo fosse XML, o final do documento poderia ser usado para sinalizar isso.

Anthony Cramp
fonte
1

Não é o Java em si, mas você pode encontrar problemas em que escreveu algo em um arquivo, mas ainda não foi realmente escrito - pode estar em um cache em algum lugar, e a leitura do mesmo arquivo pode não fornecer as novas informações.

Versão curta - use flush () ou qualquer outra chamada de sistema relevante para garantir que seus dados sejam realmente gravados no arquivo.

Nota Eu não estou falando sobre o cache de disco no nível do sistema operacional - se seus dados entrarem aqui, eles deverão aparecer em um read () após este ponto. Pode ser que a própria linguagem armazene em cache as gravações, esperando até que um buffer seja preenchido ou que o arquivo seja liberado / fechado.

Matthew Schinckel
fonte
1

Há um Java Graphic Tail de código aberto que faz isso.

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

Você não pode ler um arquivo aberto a partir de outro processo usando FileInputStream, FileReader ou RandomAccessFile.

Mas usar FileChannel diretamente funcionará:

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

Nunca tentei fazer isso, mas você deve escrever um caso de teste para ver se a leitura de um fluxo depois de chegar ao fim funcionará, independentemente de haver mais dados gravados no arquivo.

Existe um motivo pelo qual você não pode usar um fluxo de entrada / saída canalizado? Os dados estão sendo gravados e lidos no mesmo aplicativo (se sim, você possui os dados, por que precisa ler o arquivo)?

Caso contrário, talvez leia até o final do arquivo, então monitore as mudanças e busque de onde você parou e continue ... embora esteja atento às condições de corrida.

Mike Stone
fonte