web-dev-qa-db-fra.com

Est-il nécessaire de fermer chaque OutputStream et Writer imbriqués séparément?

J'écris un morceau de code:

OutputStream outputStream = new FileOutputStream(createdFile);
GZIPOutputStream gzipOutputStream = new GZIPOutputStream(outputStream);
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(gzipOutputStream));

Dois-je fermer chaque flux ou écrivain comme suit?

gzipOutputStream.close();
bw.close();
outputStream.close();

Ou va-t-il falloir fermer le dernier flux?

bw.close();
126
Adon Smith

En supposant que tous les flux soient bien créés, il suffit de fermer bw = avec ces implémentations de flux; mais c'est une grande hypothèse.

J'utiliserais try-with-resources ( tutorial ) afin que les problèmes de construction des flux suivants générant des exceptions ne laissent pas les flux précédents en suspens, Ne vous fiez pas à l'implémentation du flux pour appeler le flux sous-jacent:

try (
    OutputStream outputStream = new FileOutputStream(createdFile);
    GZIPOutputStream gzipOutputStream = new GZIPOutputStream(outputStream);
    OutputStreamWriter osw = new OutputStreamWriter(gzipOutputStream);
    BufferedWriter bw = new BufferedWriter(osw)
    ) {
    // ...
}

Notez que vous n’appelez plus du tout close.

Remarque importante : Pour que les ressources d’essai avec les ferment, vous devez affectez les flux aux variables lorsque vous les ouvrez. eux, vous ne pouvez pas utiliser la nidification. Si vous utilisez l'imbrication, une exception lors de la construction de l'un des derniers flux (par exemple, GZIPOutputStream) laissera ouverte tout flux créé par les appels imbriqués. De JLS §14.20. :

Une instruction try-with-resources est paramétrée avec des variables (appelées ressources) initialisées avant l'exécution du bloc try et fermées. automatiquement, dans l'ordre inverse de leur initialisation, après l'exécution du bloc try.

Notez le mot "variables" (mon emphase).

Par exemple, ne faites pas ceci:

// DON'T DO THIS
try (BufferedWriter bw = new BufferedWriter(
        new OutputStreamWriter(
        new GZIPOutputStream(
        new FileOutputStream(createdFile))))) {
    // ...
}

... car une exception du constructeur GZIPOutputStream(OutputStream) (qui dit qu'il peut jeter IOException et écrire un en-tête dans le flux sous-jacent) laisserait le FileOutputStream ouvert. Puisque certaines ressources ont des constructeurs qui peuvent lancer et d'autres pas, c'est une bonne habitude de simplement les lister séparément.

Nous pouvons vérifier notre interprétation de cette section de JLS avec ce programme:

public class Example {

    private static class InnerMost implements AutoCloseable {
        public InnerMost() throws Exception {
            System.out.println("Constructing " + this.getClass().getName());
        }

        @Override
        public void close() throws Exception {
            System.out.println(this.getClass().getName() + " closed");
        }
    }

    private static class Middle implements AutoCloseable {
        private AutoCloseable c;

        public Middle(AutoCloseable c) {
            System.out.println("Constructing " + this.getClass().getName());
            this.c = c;
        }

        @Override
        public void close() throws Exception {
            System.out.println(this.getClass().getName() + " closed");
            c.close();
        }
    }

    private static class OuterMost implements AutoCloseable {
        private AutoCloseable c;

        public OuterMost(AutoCloseable c) throws Exception {
            System.out.println("Constructing " + this.getClass().getName());
            throw new Exception(this.getClass().getName() + " failed");
        }

        @Override
        public void close() throws Exception {
            System.out.println(this.getClass().getName() + " closed");
            c.close();
        }
    }

    public static final void main(String[] args) {
        // DON'T DO THIS
        try (OuterMost om = new OuterMost(
                new Middle(
                    new InnerMost()
                    )
                )
            ) {
            System.out.println("In try block");
        }
        catch (Exception e) {
            System.out.println("In catch block");
        }
        finally {
            System.out.println("In finally block");
        }
        System.out.println("At end of main");
    }
}

... qui a la sortie:

 Exemple de construction $ InnerMost 
 Exemple de construction $ Middle 
 Exemple de construction $ OuterMost 
 Dans le bloc catch 
 Dans le bloc final 
 À la fin du principal 

Notez qu'il n'y a pas d'appels à close ici.

Si nous corrigeons main:

public static final void main(String[] args) {
    try (
        InnerMost im = new InnerMost();
        Middle m = new Middle(im);
        OuterMost om = new OuterMost(m)
        ) {
        System.out.println("In try block");
    }
    catch (Exception e) {
        System.out.println("In catch block");
    }
    finally {
        System.out.println("In finally block");
    }
    System.out.println("At end of main");
}

alors nous obtenons les appels close appropriés:

 Exemple de construction $ InnerMost 
 Exemple de construction $ Middle 
 Exemple de construction $ OuterMost 
 Exemple $ Middle fermé 
 Exemple $ InnerMost fermé 
 Exemple $ InnerMost fermé 
 Dans le bloc catch 
 Dans un bloc final 
 À la fin du principal 

(Oui, deux appels à InnerMost#close Sont corrects; l'un provient de Middle, l'autre de try-with-resources.)

144
T.J. Crowder

Vous pouvez fermer le flux le plus externe. En fait, vous n'avez pas besoin de conserver tous les flux encapsulés et vous pouvez utiliser Java 7 try-with-resources.

try (BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(
                     new GZIPOutputStream(new FileOutputStream(createdFile)))) {
     // write to the buffered writer
}

Si vous vous abonnez à YAGNI ou si vous n'en avez pas besoin, vous ne devriez ajouter que le code dont vous avez réellement besoin. Vous ne devriez pas ajouter du code dont vous imaginez avoir besoin, mais qui ne fait en réalité rien d’utile.

Prenez cet exemple et imaginez ce qui pourrait mal tourner si vous ne le faisiez pas et quel en serait l'impact?

try (
    OutputStream outputStream = new FileOutputStream(createdFile);
    GZIPOutputStream gzipOutputStream = new GZIPOutputStream(outputStream);
    OutputStreamWriter osw = new OutputStreamWriter(gzipOutputStream);
    BufferedWriter bw = new BufferedWriter(osw)
    ) {
    // ...
}

Commençons par FileOutputStream qui appelle open pour effectuer tout le travail réel.

/**
 * Opens a file, with the specified name, for overwriting or appending.
 * @param name name of file to be opened
 * @param append whether the file is to be opened in append mode
 */
private native void open(String name, boolean append)
    throws FileNotFoundException;

Si le fichier n'est pas trouvé, il n'y a aucune ressource sous-jacente à fermer. La fermeture ne fera donc aucune différence. Si le fichier existe, il devrait lancer une exception FileNotFoundException. Il n'y a donc rien à gagner à essayer de fermer la ressource à partir de cette ligne uniquement.

La raison pour laquelle vous devez fermer le fichier est lorsque le fichier est ouvert avec succès, mais vous obtenez plus tard une erreur.

Regardons le prochain flux GZIPOutputStream

Il y a du code qui peut lancer une exception

private void writeHeader() throws IOException {
    out.write(new byte[] {
                  (byte) GZIP_MAGIC,        // Magic number (short)
                  (byte)(GZIP_MAGIC >> 8),  // Magic number (short)
                  Deflater.DEFLATED,        // Compression method (CM)
                  0,                        // Flags (FLG)
                  0,                        // Modification time MTIME (int)
                  0,                        // Modification time MTIME (int)
                  0,                        // Modification time MTIME (int)
                  0,                        // Modification time MTIME (int)
                  0,                        // Extra flags (XFLG)
                  0                         // Operating system (OS)
              });
}

Ceci écrit l'en-tête du fichier. Maintenant, il serait très inhabituel pour vous de pouvoir ouvrir un fichier en écriture sans pouvoir y écrire même 8 octets, mais imaginons que cela puisse arriver et nous ne fermons pas le fichier par la suite. Qu'advient-il d'un fichier s'il n'est pas fermé?

Vous n'obtenez aucune écriture non vidée, elles sont rejetées et dans ce cas, il n'y a pas d'octets écrits avec succès dans le flux qui ne sont pas mis en mémoire tampon à ce stade. Mais un fichier qui n'est pas fermé ne vit pas éternellement, mais FileOutputStream a

protected void finalize() throws IOException {
    if (fd != null) {
        if (fd == FileDescriptor.out || fd == FileDescriptor.err) {
            flush();
        } else {
            /* if fd is shared, the references in FileDescriptor
             * will ensure that finalizer is only called when
             * safe to do so. All references using the fd have
             * become unreachable. We can call close()
             */
            close();
        }
    }
}

Si vous ne fermez pas du tout un fichier, il est quand même fermé, mais pas immédiatement (et comme je l'ai dit, les données laissées dans une mémoire tampon seront perdues de cette façon, mais il n'y en a aucune à ce stade).

Quelle est la conséquence de ne pas fermer le fichier immédiatement? Dans des conditions normales, vous risquez de perdre des données et de manquer de descripteurs de fichiers. Mais si vous avez un système où vous pouvez créer des fichiers mais que vous ne pouvez rien leur écrire, vous avez un problème plus grave. c’est-à-dire qu’il est difficile d’imaginer pourquoi vous essayez à plusieurs reprises de créer ce fichier malgré le fait que vous échouiez.

OutputStreamWriter et BufferedWriter ne lisent pas IOException dans leurs constructeurs, il est donc difficile de savoir quel problème elles causeraient. Dans le cas de BufferedWriter, vous pouvez obtenir une erreur OutOfMemoryError. Dans ce cas, il va immédiatement déclencher un GC qui, comme nous l’avons vu, fermera quand même le fichier.

12
Peter Lawrey

Je préférerais utiliser la syntaxe try(...) (Java 7), par exemple.

try (OutputStream outputStream = new FileOutputStream(createdFile)) {
      ...
}
6
Dmitry Bychenko

Si tous les flux ont été instanciés, alors fermer uniquement le plus externe est parfait.

La documentation sur Closeable indique que la méthode close est la suivante:

Ferme ce flux et libère les ressources système qui lui sont associées.

La libération des ressources système inclut la fermeture des flux.

Il indique également que:

Si le flux est déjà fermé, l'invocation de cette méthode n'a aucun effet.

Donc, si vous les fermez explicitement par la suite, rien ne se passera de mal.

6
Grzegorz Żur

Non, le niveau le plus élevé Stream ou reader garantira la fermeture de tous les flux sous-jacents.

Vérifiez la méthode close()implémentation de votre flux de niveau le plus élevé.

5
TheLostMind

Dans Java 7, il existe une fonctionnalité try-with-resources) . Vous n'avez pas besoin de fermer explicitement vos flux, il s'en chargera.

5
Sivakumar

Si vous ne fermez que le dernier flux, tout ira bien - l'appel de clôture sera également envoyé aux flux sous-jacents.

5
Codeversum