92 votes

Façon correcte de fermer les flux et les écrivains imbriqués en Java

Remarque : Cette question et la plupart de ses réponses datent d'avant la sortie de Java 7. Java 7 fournit Gestion automatique des ressources fonctionnalité pour faire cela facilement. Si vous utilisez Java 7 ou une version ultérieure, vous devriez passer à la réponse de Ross Johnson .


Quelle est la meilleure façon, la plus complète, de fermer des flux imbriqués en Java ? Prenons l'exemple suivant :

FileOutputStream fos = new FileOutputStream(...)
BufferedOS bos = new BufferedOS(fos);
ObjectOutputStream oos = new ObjectOutputStream(bos);

Je comprends que l'opération de clôture doit être assurée (probablement en utilisant une clause finally). Ce que je me demande, c'est s'il est nécessaire de s'assurer explicitement que les flux imbriqués sont fermés, ou s'il suffit de s'assurer de fermer le flux extérieur (oos) ?

Une chose que j'ai remarquée, du moins en ce qui concerne cet exemple spécifique, c'est que les flux internes ne semblent lancer que des exceptions de type FileNotFound. Ce qui semble impliquer qu'il n'y a pas techniquement besoin de s'inquiéter de les fermer s'ils échouent.

Voici ce qu'a écrit un collègue :


Techniquement, s'il était mis en œuvre correctement, le fait de fermer le (oos) devrait suffire. Mais l'implémentation semble défectueuse.

Exemple : BufferedOutputStream hérite de close() de FilterOutputStream, qui la définit comme suit :

 155       public void close() throws IOException {
 156           try {
 157             flush();
 158           } catch (IOException ignored) {
 159           }
 160           out.close();
 161       }

Cependant, si flush() lève une exception d'exécution pour une raison quelconque, alors out.close() ne sera jamais appelée. Il semble donc plus "sûr" (mais moche) de se préoccuper principalement de la fermeture de FOS, qui consiste à garder le fichier ouvert.


Quelle est la meilleure approche pour fermer les flux imbriqués lorsque l'on a absolument besoin d'être sûr de soi ?

Existe-t-il des documents officiels Java/Sun qui traitent de ce sujet en détail ?

40voto

Bill the Lizard Points 147311

Lorsque vous fermez des flux enchaînés, vous ne devez fermer que le flux le plus externe. Toute erreur sera propagée le long de la chaîne et sera détectée.

Se référer à Flux d'E/S Java pour plus de détails.

Pour aborder la question

~~

Cependant, si flush() lève une exception d'exécution pour une raison quelconque, out.close() ne sera jamais appelé.

~~

Ce n'est pas normal. Après avoir attrapé et ignoré cette exception, l'exécution reprendra après le bloc catch et le bloc out.close() sera exécutée.

Votre collègue a soulevé un point important concernant la Temps d'exécution Exception. Si vous avez absolument besoin de fermer le flux, vous pouvez toujours essayer de fermer chaque flux individuellement, de l'extérieur vers l'intérieur, en vous arrêtant à la première exception.

27voto

Ross Johnson Points 31

À l'ère de Java 7, Essayer avec des ressources est certainement la meilleure solution. Comme indiqué dans plusieurs réponses précédentes, la demande de fermeture se propage du flux extérieur au flux intérieur. Une seule fermeture suffit donc.

try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(f))) {
  // do something with ois
}

Ce modèle pose toutefois un problème. Le try-with-resources n'est pas conscient du FileInputStream interne, donc si le constructeur de l'ObjectInputStream lève une exception, le FileInputStream n'est jamais fermé (jusqu'à ce que le garbage collector s'en occupe). La solution est...

try (FileInputStream fis = new FileInputStream(f); ObjectInputStream ois = new ObjectInputStream(fis)) {
  // do something with ois
}

Cette solution n'est pas aussi élégante, mais elle est plus robuste. La question de savoir si cela pose réellement un problème dépend des exceptions qui peuvent être levées lors de la construction de l'objet extérieur. ObjectInputStream peut lancer une exception IO qui peut être traitée par une application sans se terminer. De nombreuses classes de flux ne lancent que des exceptions non contrôlées, ce qui peut entraîner l'arrêt de l'application.

21voto

Rulix Batistil Points 81

Il est conseillé d'utiliser Apache Commons pour gérer les objets liés aux entrées-sorties.

Dans le cadre de la finally utilisation de la clause IOUtils

IOUtils.closeQuietly(bWriter) ; IOUtils.closeQuietly(oWritter) ;

Extrait de code ci-dessous.

BufferedWriter bWriter = null;
OutputStreamWriter oWritter = null;

try {
  oWritter  = new OutputStreamWriter( httpConnection.getOutputStream(), "utf-8" );
  bWriter = new BufferedWriter( oWritter );
  bWriter.write( xml );
}
finally {
  IOUtils.closeQuietly(bWriter);
  IOUtils.closeQuietly(oWritter);
}

19voto

Scott Stanchfield Points 15863

Je procède généralement de la manière suivante. Tout d'abord, je définis une classe basée sur un modèle de méthode pour gérer le désordre des try/catch.

import java.io.Closeable;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;

public abstract class AutoFileCloser {
    // the core action code that the implementer wants to run
    protected abstract void doWork() throws Throwable;

    // track a list of closeable thingies to close when finished
    private List<Closeable> closeables_ = new LinkedList<Closeable>();

    // give the implementer a way to track things to close
    // assumes this is called in order for nested closeables,
    // inner-most to outer-most
    protected final <T extends Closeable> T autoClose(T closeable) {
            closeables_.add(0, closeable);
            return closeable;
    }

    public AutoFileCloser() {
        // a variable to track a "meaningful" exception, in case
        // a close() throws an exception
        Throwable pending = null;

        try {
            doWork(); // do the real work

        } catch (Throwable throwable) {
            pending = throwable;

        } finally {
            // close the watched streams
            for (Closeable closeable : closeables_) {
                if (closeable != null) {
                    try {
                        closeable.close();
                    } catch (Throwable throwable) {
                        if (pending == null) {
                            pending = throwable;
                        }
                    }
                }
            }

            // if we had a pending exception, rethrow it
            // this is necessary b/c the close can throw an
            // exception, which would remove the pending
            // status of any exception thrown in the try block
            if (pending != null) {
                if (pending instanceof RuntimeException) {
                    throw (RuntimeException) pending;
                } else {
                    throw new RuntimeException(pending);
                }
            }
        }
    }
}

Notez l'exception "pending" (en attente) -- cela permet d'éviter qu'une exception lancée lors de la fermeture ne masque une exception qui nous intéresse vraiment.

The finally essaie de fermer d'abord le flux décoré depuis l'extérieur, donc si vous avez un BufferedWriter enveloppant un FileWriter, nous essayons de fermer d'abord le BuffereredWriter, et si cela échoue, nous essayons encore de fermer le FileWriter lui-même. (Notez que la définition de Closeable demande à close() d'ignorer l'appel si le flux est déjà fermé).

Vous pouvez utiliser la classe ci-dessus comme suit :

try {
    // ...

    new AutoFileCloser() {
        @Override protected void doWork() throws Throwable {
            // declare variables for the readers and "watch" them
            FileReader fileReader = 
                    autoClose(fileReader = new FileReader("somefile"));
            BufferedReader bufferedReader = 
                    autoClose(bufferedReader = new BufferedReader(fileReader));

            // ... do something with bufferedReader

            // if you need more than one reader or writer
            FileWriter fileWriter = 
                    autoClose(fileWriter = new FileWriter("someOtherFile"));
            BufferedWriter bufferedWriter = 
                    autoClose(bufferedWriter = new BufferedWriter(fileWriter));

            // ... do something with bufferedWriter
        }
    };

    // .. other logic, maybe more AutoFileClosers

} catch (RuntimeException e) {
    // report or log the exception
}

Grâce à cette approche, vous n'aurez plus jamais à vous préoccuper de l'approche try/catch/finally pour gérer la fermeture des fichiers.

Si cela est trop lourd pour votre utilisation, pensez au moins à suivre l'approche try/catch et la variable "pending" qu'elle utilise.

5voto

Cette question est étonnamment embarrassante. (Même en supposant que le acquire; try { use; } finally { release; } est correct).

Si la construction du décorateur échoue, vous ne fermerez pas le flux sous-jacent. Vous devez donc fermer le flux sous-jacent de manière explicite, que ce soit finalement après utilisation ou, plus difficilement, après avoir transmis avec succès la ressource au décorateur.)

Si une exception entraîne l'échec de l'exécution, souhaitez-vous vraiment rincer le système ?

Certains décorateurs disposent eux-mêmes de ressources. L'implémentation Sun actuelle de ZipInputStream par exemple, dispose d'une mémoire de tas non Java.

Il a été affirmé que (IIRC) deux tiers des ressources utilisées dans la bibliothèque Java sont mises en œuvre d'une manière manifestement incorrecte.

Tout en BufferedOutputStream se ferme même sur un IOException de flush , BufferedWriter se ferme correctement.

Mon conseil : Fermez les ressources aussi directement que possible et ne les laissez pas entacher d'autres codes. D'un autre côté, vous pouvez passer trop de temps sur cette question - si OutOfMemoryError est lancé, il est agréable de se comporter gentiment, mais d'autres aspects de votre programme sont probablement plus prioritaires et le code de la bibliothèque est probablement cassé dans cette situation de toute façon. Mais j'écrirais toujours :

final FileOutputStream rawOut = new FileOutputStream(file);
try {
    OutputStream out = new BufferedOutputStream(rawOut);
    ... write stuff out ...
    out.flush();
} finally {
    rawOut.close();
}

(Regardez : pas de prise !)

Et peut-être utiliser l'idiome Execute Around.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X