Comment utiliser Java pour lire à partir d'un fichier en cours d'écriture?

99

J'ai une application qui écrit des informations dans un fichier. Ces informations sont utilisées après l'exécution pour déterminer la réussite / l'échec / l'exactitude de l'application. J'aimerais pouvoir lire le fichier au fur et à mesure de son écriture afin de pouvoir faire ces vérifications de réussite / échec / exactitude en temps réel.

Je suppose qu'il est possible de le faire, mais quels sont les pièges lors de l'utilisation de Java? Si la lecture rattrape l'écriture, attendra-t-elle simplement plus d'écritures jusqu'à ce que le fichier soit fermé, ou la lecture lèvera-t-elle une exception à ce stade? Si ce dernier, que dois-je faire alors?

Mon intuition me pousse actuellement vers BufferedStreams. Est-ce la voie à suivre?

Anthony Cramp
la source
1
hé, comme je suis confronté à un scénario similaire, je formulais si vous avez trouvé une meilleure solution que celle acceptée?
Asaf David
Je sais que c'est une vieille question, mais pour le bien des futurs lecteurs, pouvez-vous développer un peu plus votre cas d'utilisation? Sans avoir plus d'informations, on se demande si vous résolvez peut-être le mauvais problème.
user359996
Essayez d'utiliser le Tailer d'Apache Commons IO. Il gère la plupart des cas de bord.
Joshua
2
Utilisez une base de données. Ces scénarios «lire un fichier pendant son écriture» se terminent en larmes.
Marquis of Lorne
@EJP - quelle base de données recommandez-vous? Je suppose que MySQL est un bon début?
Café du

Réponses:

39

Impossible de faire fonctionner l'exemple FileChannel.read(ByteBuffer)car il ne s'agit pas d'une lecture bloquante. J'ai cependant fait fonctionner le code ci-dessous:

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

Bien sûr, la même chose fonctionnerait comme une minuterie au lieu d'un thread, mais je laisse cela au programmeur. Je cherche toujours un meilleur moyen, mais cela fonctionne pour moi pour le moment.

Oh, et je vais mettre cela en garde avec: j'utilise la version 1.4.2. Oui, je sais que je suis encore à l'âge de pierre.

Joseph Gordon
la source
1
Merci d'avoir ajouté ceci ... quelque chose que je n'ai jamais réussi à faire. Je pense que la réponse de Blade de verrouiller le fichier est également bonne. Cependant, il nécessite Java 6 (je pense).
Anthony Cramp
@JosephGordon - Vous devrez passer à l'âge de drone un de ces jours ;-)
TungstenX
12

Si vous souhaitez lire un fichier pendant qu'il est en cours d'écriture et ne lire que le nouveau contenu, ce qui suit vous aidera à réaliser la même chose.

Pour exécuter ce programme, vous le lancerez à partir de l'invite de commande / de la fenêtre du terminal et passerez le nom du fichier à lire. Il lira le fichier à moins que vous ne supprimiez le programme.

java FileReader c: \ myfile.txt

Lorsque vous tapez une ligne de texte, enregistrez-la à partir du bloc-notes et vous verrez le texte imprimé dans la 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
la source
2
N'y a-t-il pas la possibilité qu'entre le moment où le fichier est lu et le moment où la longueur du fichier est prise, le processus externe puisse ajouter plus de données au fichier. Si tel est le cas, cela entraînerait le processus de lecture des données manquantes écrites dans le fichier.
JohnC
1
Ce code consomme beaucoup de CPU car la boucle ne contient pas d'appel thread.sleep. Sans ajouter un petit délai, ce code a tendance à maintenir le processeur très occupé.
ChaitanyaBhatt
Les deux commentaires ci-dessus sont vrais et en plus cette BufferedReadercréation dans chaque appel de boucle est tellement inutile. Avec sa création une fois, le saut ne serait pas nécessaire, car le lecteur tamponné lirait les nouvelles lignes à mesure qu'elles arrivent.
Piotr
5

Je suis totalement d'accord avec la réponse de Joshua , Tailer est apte à faire le travail dans cette situation. Voici un exemple :

Il écrit une ligne toutes les 150 ms dans un fichier, tout en lisant ce même fichier toutes les 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
la source
Votre lien vers Tailer est rompu.
Stealth Rabbi
2
@StealthRabbi La meilleure chose à faire est alors de rechercher le lien correct et de modifier la réponse avec.
igracia
3

La réponse semble être "non" ... et "oui". Il ne semble y avoir aucun moyen réel de savoir si un fichier est ouvert à l'écriture par une autre application. Ainsi, la lecture d'un tel fichier progressera jusqu'à ce que le contenu soit épuisé. J'ai suivi les conseils de Mike et j'ai écrit du code de test:

Writer.java écrit une chaîne dans le fichier, puis attend que l'utilisateur appuie sur Entrée avant d'écrire une autre ligne dans le fichier. L'idée étant qu'il puisse être démarré, alors un lecteur peut être démarré pour voir comment il gère le fichier "partiel". Le lecteur que j'ai écrit est dans 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);
    }
}

Aucune garantie que ce code est la meilleure pratique.

Cela laisse l'option suggérée par Mike de vérifier périodiquement s'il y a de nouvelles données à lire à partir du fichier. Cela nécessite alors une intervention de l'utilisateur pour fermer le lecteur de fichiers lorsqu'il est déterminé que la lecture est terminée. Ou bien, le lecteur doit être informé du contenu du fichier et être en mesure de déterminer la condition de fin d'écriture. Si le contenu était XML, la fin du document pourrait être utilisée pour le signaler.

Anthony Cramp
la source
1

Pas Java en soi, mais vous pouvez rencontrer des problèmes où vous avez écrit quelque chose dans un fichier, mais il n'a pas encore été écrit - il peut être dans un cache quelque part, et la lecture à partir du même fichier peut ne pas vous donner réellement les nouvelles informations.

Version courte - utilisez flush () ou tout autre appel système pertinent pour vous assurer que vos données sont réellement écrites dans le fichier.

Remarque Je ne parle pas du cache disque au niveau du système d'exploitation - si vos données entrent ici, elles devraient apparaître dans un read () après ce point. Il se peut que le langage lui-même mette en cache les écritures, en attendant qu'un tampon se remplisse ou que le fichier soit vidé / fermé.

Matthew Schinckel
la source
1

Il existe une queue graphique Java Open Source qui fait cela.

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
la source
1

Vous ne pouvez pas lire un fichier ouvert à partir d'un autre processus à l'aide de FileInputStream, FileReader ou RandomAccessFile.

Mais utiliser FileChannel directement fonctionnera:

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
la source
0

Je ne l'ai jamais essayé, mais vous devriez écrire un cas de test pour voir si la lecture d'un flux après avoir atteint la fin fonctionnera, indépendamment du fait qu'il y ait plus de données écrites dans le fichier.

Y a-t-il une raison pour laquelle vous ne pouvez pas utiliser un flux d'entrée / sortie canalisé? Les données sont-elles écrites et lues à partir de la même application (si oui, vous avez les données, pourquoi avez-vous besoin de lire à partir du fichier)?

Sinon, lisez peut-être jusqu'à la fin du fichier, puis surveillez les changements et cherchez là où vous vous êtes arrêté et continuez ... mais faites attention aux conditions de course.

Mike Stone
la source