web-dev-qa-db-fra.com

Manière 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 publication de Java 7. Java 7 fournit Gestion automatique des ressources une fonctionnalité permettant de le faire facilement. Si vous utilisez Java 7 ou une version ultérieure, passez à la réponse de Ross Johnson .


Quel est le moyen le plus complet et le plus complet de fermer les flux imbriqués en Java? Par exemple, considérons la configuration:

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

Je comprends que l’opération rapprochée doit être assurée (probablement en utilisant une clause finale). La question que je me pose est la suivante: faut-il explicitement s’assurer que les flux imbriqués sont fermés ou s’il suffit de s’assurer de fermer le flux externe (oos)?

Une chose que je remarque, du moins en ce qui concerne cet exemple spécifique, est que les flux internes ne semblent lancer que FileNotFoundExceptions. Ce qui semble impliquer qu’il n’est pas techniquement nécessaire de s’inquiéter de leur fermeture en cas d’échec.

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


Techniquement, si elle était mise en œuvre correctement, la fermeture de l’extérieurstream (oos) devrait suffire. Mais la mise en œuvre semble imparfaite.

Exemple: BufferedOutputStream hérite de close () de FilterOutputStream, qui le 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é. Cela semble donc "le plus sûr" (mais moche) de Principalement s’inquiéter de la fermeture de FOS, qui garde le fichier ouvert.


Qu'est-ce qui est considéré comme la meilleure approche, quand vous avez absolument besoin d'être sûr, pour fermer les flux imbriqués?

Et existe-t-il des documents Java/Sun officiels traitant de cette question en détail?

90
dirtyvagabond

Je fais habituellement ce qui suit. Tout d’abord, définissez une classe basée sur un modèle pour gérer le désordre 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 "en attente" - cela prend en charge le cas où une exception levée lors de la fermeture masquerait une exception dont nous pourrions vraiment nous soucier.

Enfin, il essaie d’abord de fermer tout flux décoré. Par conséquent, si vous avez un BufferedWriter enveloppant un FileWriter, nous essayons de fermer d’abord le BuffereredWriter, et si cela échoue, essayez quand même de le fermer lui-même. (Notez que la définition d'appels pouvant être fermés pour close () 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
}

En utilisant cette approche, vous n’aurez plus jamais à vous soucier de try/catch/finally pour traiter à nouveau la fermeture de fichiers.

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

18

Lors de la fermeture de flux chaînés, il vous suffit de fermer le flux le plus externe. Toutes les erreurs seront propagées dans la chaîne et seront attrapées.

Reportez-vous à Java I/O Streams pour plus de détails.

Pour régler le problème

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

Ce n'est pas bien. Après que vous ayez capturé et ignoré cette exception, l'exécution reprendra après le bloc catch et l'instruction out.close() sera exécutée. </ Strike>

Votre collègue soulève un bon point à propos de l'exception Runtime. Si vous avez absolument besoin que le flux soit fermé, vous pouvez toujours essayer de les fermer individuellement, de l'extérieur vers l'intérieur, en vous arrêtant à la première exception.

39
Bill the Lizard

À l'ère Java 7, essayez-avec-les ressources est certainement la voie à suivre. Comme mentionné dans plusieurs réponses précédentes, la demande de fermeture se propage du flux le plus externe au flux le plus interne. Donc, une seule fermeture est tout ce qui est requis.

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

Il y a cependant un problème avec ce modèle. Try-with-resources n'est pas au courant du FileInputStream interne. Par conséquent, si le constructeur ObjectInputStream lève une exception, le FileInputStream n'est jamais fermé (jusqu'à ce que le récupérateur de déchets le reçoive). La solution est ...

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

Ce n'est pas aussi élégant, mais est plus robuste. Que ce soit réellement un problème ou non dépendra des exceptions pouvant être levées lors de la construction du ou des objets extérieurs. ObjectInputStream peut générer une exception IOException qui peut être gérée par une application sans se terminer. De nombreuses classes de flux ne lisent que des exceptions non vérifiées, ce qui peut entraîner la fermeture de l'application.

25
Ross Johnson

Il est recommandé d’utiliser Apache Commons pour gérer les objets liés à IO.

Dans la clause finally, utilisez IOUtils 

IUtils.closeQuietly (BWriter);

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);
}
21
Rulix Batistil

C'est une question étonnamment délicate. (Même en supposant que le code acquire; try { use; } finally { release; } est correct.)

Si la construction du décorateur échoue, vous ne fermerez pas le flux sous-jacent. Par conséquent, vous devez fermer explicitement le flux sous-jacent, que ce soit lors de l'utilisation finale ou plus difficile après la remise de la ressource au décorateur).

Si une exception entraîne l'échec de l'exécution, voulez-vous vraiment vider?

Certains décorateurs ont eux-mêmes des ressources. L'implémentation Sun actuelle de ZipInputStream, par exemple, a une mémoire de segment non Java allouée.

Il a été prétendu que (IIRC) les deux tiers des ressources utilisées dans la bibliothèque Java sont implémentées de manière clairement incorrecte.

Alors que 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 altérer les autres codes. OTOH, vous pouvez passer trop de temps sur cette question - si OutOfMemoryError est lancé, il est agréable de bien se comporter, mais d’autres aspects de votre programme ont probablement une priorité plus élevée et le code de 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();
}

(Regarde: Pas de piège!)

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

5

Le collègue soulève un point intéressant, et il y a des raisons de discuter des deux côtés.

Personnellement, j'ignorerais la RuntimeException, car une exception non contrôlée signifie un bogue dans le programme. Si le programme est incorrect, corrigez-le. Vous ne pouvez pas "gérer" un mauvais programme au moment de l'exécution.

5
erickson

Java SE 7 try-with-resources ne semble pas être mentionné. Cela élimine le besoin de faire explicitement un rapprochement complet, et j'aime bien l'idée.

Malheureusement, pour le développement d'Android, cette application n'est disponible qu'en utilisant Android Studio (je pense) et ciblant KitKat et versions ultérieures .

1
akauppi

J'utilise pour fermer les flux comme ceci, sans imbriquer try-catch dans blocks

public class StreamTest {

public static void main(String[] args) {

    FileOutputStream fos = null;
    BufferedOutputStream bos = null;
    ObjectOutputStream oos = null;

    try {
        fos = new FileOutputStream(new File("..."));
        bos = new BufferedOutputStream(fos);
        oos = new ObjectOutputStream(bos);
    }
    catch (Exception e) {
    }
    finally {
        Stream.close(oos,bos,fos);
    }
  }   
}

class Stream {

public static void close(AutoCloseable... array) {
    for (AutoCloseable c : array) {
        try {c.close();}
        catch (IOException e) {}
        catch (Exception e) {}
    }
  } 
}
0
Noor Nawaz

Aussi, vous n'avez pas à fermer tous les flux imbriqués

vérifier ceci http://ckarthik17.blogspot.com/2011/02/closing-nested-streams.html

0
Karthik Chandraraj