Piszę kawałek kodu:
OutputStream outputStream = new FileOutputStream(createdFile);
GZIPOutputStream gzipOutputStream = new GZIPOutputStream(outputStream);
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(gzipOutputStream));
Czy muszę zamykać każdy strumień lub program zapisujący, jak poniżej?
gzipOutputStream.close();
bw.close();
outputStream.close();
A może po prostu zamknięcie ostatniego strumienia będzie w porządku?
bw.close();
java
file-io
outputstream
writer
Adon Smith
źródło
źródło
BufferedWriter
może być konieczne zapisanie buforowanych danych w źródłowym strumieniu, który w Twoim przykładzie jest już zamknięty. Unikanie tych problemów to kolejna zaleta podejść opartych na próbach z zasobami przedstawionych w odpowiedziach.Odpowiedzi:
Zakładając, że wszystkie strumienie zostały utworzone w porządku, tak, samo zamknięcie
bw
jest w porządku w przypadku tych implementacji strumieni ; ale to duże założenie.Użyłbym try-with-resources ( tutorial ), aby wszelkie problemy z konstruowaniem kolejnych strumieni, które generują wyjątki, nie pozostawiły poprzednich strumieni w zawieszeniu, więc nie musisz polegać na implementacji strumienia, która ma wywołanie do zamknięcia podstawowy strumień:
try ( OutputStream outputStream = new FileOutputStream(createdFile); GZIPOutputStream gzipOutputStream = new GZIPOutputStream(outputStream); OutputStreamWriter osw = new OutputStreamWriter(gzipOutputStream); BufferedWriter bw = new BufferedWriter(osw) ) { // ... }
Zauważ, że już
close
w ogóle nie dzwonisz .Ważna uwaga : aby zamykać je z zasobami typu „próba z zasobami”, należy przypisać strumienie do zmiennych podczas ich otwierania, nie można używać zagnieżdżania. Jeśli używasz zagnieżdżania, wyjątek podczas konstruowania jednego z późniejszych strumieni (powiedzmy
GZIPOutputStream
) pozostawi otwarty każdy strumień utworzony przez zagnieżdżone wywołania wewnątrz niego. Od JLS §14.20.3 :Zwróć uwagę na słowo „zmienne” (moje wyróżnienie) .
Np. Nie rób tego:
// DON'T DO THIS try (BufferedWriter bw = new BufferedWriter( new OutputStreamWriter( new GZIPOutputStream( new FileOutputStream(createdFile))))) { // ... }
... ponieważ wyjątek od
GZIPOutputStream(OutputStream)
konstruktora (który mówi, że może zgłosićIOException
i zapisuje nagłówek do źródłowego strumienia) pozostawiłbyFileOutputStream
otwarty. Ponieważ niektóre zasoby mają konstruktory, które mogą rzucać, a inne nie, dobrym zwyczajem jest umieszczanie ich osobno.Możemy dwukrotnie sprawdzić naszą interpretację tej sekcji JLS za pomocą tego programu:
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"); } }
... który ma wyjście:
Zauważ, że nie ma tam żadnych połączeń
close
.Jeśli naprawimy
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"); }
wtedy otrzymujemy odpowiednie
close
wezwania:(Tak, dwa wywołania
InnerMost#close
są poprawne; jedno pochodzi zMiddle
, a drugie z try-with-resources.)źródło
java.io
. Niektóre strumienie - uogólniając, niektóre zasoby - wyrzucają od konstruktorów. Tak więc upewnianie się, że wiele zasobów jest otwieranych indywidualnie, aby można je było niezawodnie zamknąć, jeśli kolejny wyrzucony zasób jest moim zdaniem dobrym nawykiem. Możesz tego nie robić, jeśli się nie zgadzasz, w porządku.Możesz zamknąć najbardziej zewnętrzny strumień, w rzeczywistości nie musisz zachowywać wszystkich opakowanych strumieni i możesz użyć Java 7 try-with-resources.
try (BufferedWriter bw = new BufferedWriter(new OutputStreamWriter( new GZIPOutputStream(new FileOutputStream(createdFile)))) { // write to the buffered writer }
Jeśli subskrybujesz YAGNI lub nie będziesz go potrzebować, powinieneś dodawać tylko kod, którego faktycznie potrzebujesz. Nie powinieneś dodawać kodu, który Twoim zdaniem może być potrzebny, ale w rzeczywistości nie robi nic pożytecznego.
Weź ten przykład i wyobraź sobie, co mogłoby się nie udać, gdybyś tego nie zrobił i jaki byłby tego wpływ?
try ( OutputStream outputStream = new FileOutputStream(createdFile); GZIPOutputStream gzipOutputStream = new GZIPOutputStream(outputStream); OutputStreamWriter osw = new OutputStreamWriter(gzipOutputStream); BufferedWriter bw = new BufferedWriter(osw) ) { // ... }
Zacznijmy od FileOutputStream, który wywołuje
open
całą prawdziwą pracę./** * 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;
Jeśli plik nie zostanie znaleziony, nie ma bazowego zasobu do zamknięcia, więc zamknięcie go nie spowoduje żadnej różnicy. Jeśli plik istnieje, powinien generować wyjątek FileNotFoundException. Nie ma więc nic do zyskania, próbując zamknąć zasób tylko z tej linii.
Powodem, dla którego musisz zamknąć plik, jest pomyślne otwarcie pliku, ale później pojawia się błąd.
Spójrzmy na następny strumień
GZIPOutputStream
Istnieje kod, który może zgłosić wyjątek
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) }); }
To zapisuje nagłówek pliku. Teraz byłoby bardzo nietypowe, gdybyś mógł otworzyć plik do zapisu, ale nie byłbyś w stanie zapisać do niego nawet 8 bajtów, ale wyobraźmy sobie, że może się to zdarzyć i nie zamykamy pliku później. Co się dzieje z plikiem, jeśli nie jest zamknięty?
Nie dostajesz żadnych niezapisanych zapisów, są one odrzucane iw tym przypadku nie ma pomyślnie zapisanych bajtów do strumienia, który i tak nie jest w tym momencie buforowany. Ale plik, który nie jest zamknięty, nie żyje wiecznie, zamiast tego FileOutputStream ma
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(); } } }
Jeśli w ogóle nie zamkniesz pliku, i tak zostanie on zamknięty, ale nie od razu (i jak powiedziałem, dane pozostawione w buforze zostaną w ten sposób utracone, ale w tym momencie ich nie ma)
Jakie są konsekwencje braku natychmiastowego zamknięcia pliku? W normalnych warunkach potencjalnie utracisz część danych i potencjalnie zabraknie deskryptorów plików. Ale jeśli masz system, w którym możesz tworzyć pliki, ale nie możesz nic do nich pisać, masz większy problem. tj. trudno sobie wyobrazić, dlaczego wielokrotnie próbujesz utworzyć ten plik, mimo że nie udaje Ci się.
Zarówno OutputStreamWriter, jak i BufferedWriter nie zgłaszają wyjątku IOException w swoich konstruktorach, więc nie jest jasne, jaki problem spowodowałby. W przypadku BufferedWriter możesz uzyskać OutOfMemoryError. W tym przypadku natychmiast uruchomi GC, który, jak widzieliśmy, i tak zamknie plik.
źródło
GZIPOutputStream(OutputStream)
dokumentyIOException
i patrząc na źródło, w rzeczywistości pisze nagłówek. Więc to nie jest teoria, że konstruktor może rzucać. Możesz uznać, że pozostawienie instrumentu bazowegoFileOutputStream
otwartego po zapisaniu do niego jest w porządku . Ja nie.Jeśli wszystkie strumienie zostały utworzone, zamknięcie tylko najbardziej zewnętrznego jest w porządku.
Dokumentacja dotycząca
Closeable
interfejsu stwierdza, że metoda close:Zwalnianie zasobów systemowych obejmuje strumienie zamykające.
Stwierdza również, że:
Więc jeśli później je zamkniesz, nic złego się nie stanie.
źródło
Wolałbym raczej używać
try(...)
składni (Java 7), nptry (OutputStream outputStream = new FileOutputStream(createdFile)) { ... }
źródło
Będzie dobrze, jeśli zamkniesz tylko ostatni strumień - wywołanie zamknięcia zostanie również wysłane do strumieni bazowych.
źródło
Nie, najwyższy poziom
Stream
lubreader
zapewni, że wszystkie podstawowe strumienie / czytniki są zamknięte.Sprawdź implementację
close()
metody swojego strumienia najwyższego poziomu.źródło
W Javie 7 istnieje funkcja wypróbuj z zasobami . Nie musisz wyraźnie zamykać strumieni, zajmie się tym.
źródło