क्या प्रत्येक नेस्टेड आउटपुटस्ट्रीम और राइटर को अलग से बंद करना आवश्यक है?


127

मैं एक कोड लिख रहा हूँ:

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

क्या मुझे निम्नलिखित की तरह हर धारा या लेखक को बंद करने की आवश्यकता है?

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

या बस अंतिम धारा को बंद करना ठीक होगा?

bw.close();

1
इसी अप्रचलित जावा 6 प्रश्न के लिए, stackoverflow.com/questions/884007/…
Raedwald

2
ध्यान दें कि आपके उदाहरण में एक बग है जो डेटा हानि का कारण बन सकता है, क्योंकि आप उन धाराओं को बंद कर रहे हैं, जिन्हें आपने उन्हें खोला नहीं था। BufferedWriterइसे बंद करते समय इसे अंतर्निहित धारा में बफर डेटा लिखना पड़ सकता है , जो आपके उदाहरण में पहले से ही बंद है। इन समस्याओं से बचने के जवाब में दिखाए गए संसाधनों के साथ प्रयास करने का एक और फायदा है।
२३

जवाबों:


150

यह मानते हुए कि सभी धाराएँ ठीक हैं, हाँ, बस बंद करना उन धारा के कार्यान्वयन के साथbw ठीक है ; लेकिन यह एक बड़ी धारणा है।

मैं कोशिश के साथ-संसाधनों ( ट्यूटोरियल ) का उपयोग करता हूं ताकि बाद के स्ट्रीम का निर्माण करने वाली कोई भी समस्या जो अपवादों को फेंकती है, पिछली धाराओं को लटकाए नहीं छोड़ती है, और इसलिए आपको कॉल को बंद करने के लिए स्ट्रीम कार्यान्वयन पर भरोसा करने की आवश्यकता नहीं है। अंतर्निहित स्ट्रीम:

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

ध्यान दें अब आप बिलकुल भी कॉल न करें close

महत्वपूर्ण सूचना : उन्हें बंद करने के लिए संसाधनों के साथ प्रयास करने के लिए, आपको उन्हें खोलते समय धाराओं को असाइन करना होगा , आप नेस्टिंग का उपयोग नहीं कर सकते। यदि आप नेस्टिंग का उपयोग करते हैं, तो बाद की धाराओं में से एक के निर्माण के दौरान एक अपवाद (कहते हैं GZIPOutputStream), नेस्टेड कॉल द्वारा निर्मित किसी भी स्ट्रीम को उसके अंदर खुला छोड़ देगा। से JLS §14.20.3 :

एक प्रयास के साथ संसाधनों के विवरण को चर (संसाधनों के रूप में जाना जाता है) के साथ परिचालित किया जाता है, जिसे tryब्लॉक के निष्पादन से पहले आरंभीकृत किया जाता है और स्वचालित रूप से बंद कर दिया जाता है, जिस क्रम में tryब्लॉक के निष्पादन के बाद उन्हें आरंभीकृत किया गया था ।

शब्द "चर" (मेरा जोर) पर ध्यान दें ।

जैसे, यह मत करो:

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

... क्योंकि GZIPOutputStream(OutputStream)कंस्ट्रक्टर से एक अपवाद (जो कहता है कि यह फेंक सकता है IOException, और अंतर्निहित स्ट्रीम पर हेडर लिखता है) FileOutputStreamखुले को छोड़ देगा । चूंकि कुछ संसाधनों में कंस्ट्रक्टर होते हैं जो फेंक सकते हैं और अन्य नहीं करते हैं, इसलिए उन्हें अलग से सूचीबद्ध करना एक अच्छी आदत है।

हम इस कार्यक्रम के साथ उस JLS खंड की हमारी व्याख्या की दोबारा जाँच कर सकते हैं:

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

... जिसका आउटपुट है:

उदाहरण $ इनरमोस्ट का निर्माण
निर्माण उदाहरण $ मध्य
उदाहरण $ OuterMost का निर्माण
पकड़ ब्लॉक में
अंत में ब्लॉक करें
मुख्य के अंत में

ध्यान दें कि वहाँ कोई कॉल नहीं है close

यदि हम ठीक करते हैं 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");
}

तब हमें उपयुक्त closeकॉल मिलती हैं:

उदाहरण $ इनरमोस्ट का निर्माण
निर्माण उदाहरण $ मध्य
उदाहरण $ OuterMost का निर्माण
उदाहरण $ मध्य बंद
उदाहरण $ इनरमोस्ट बंद
उदाहरण $ इनरमोस्ट बंद
पकड़ ब्लॉक में
अंत में ब्लॉक करें
मुख्य के अंत में

(हां, दो कॉल InnerMost#closeसही हैं; एक है Middle, दूसरे से कोशिश-संसाधनों के साथ।)


7
+1 यह ध्यान देने के लिए कि धाराओं के निर्माण के दौरान अपवाद फेंके जा सकते हैं, हालांकि मैं ध्यान दूंगा कि वास्तविक रूप से आप या तो आउट-ऑफ-मेमोरी अपवाद या समान रूप से गंभीर कुछ प्राप्त करने जा रहे हैं (जिस बिंदु पर यह वास्तव में मायने नहीं रखता है यदि आप अपनी धाराओं को बंद कर देते हैं, क्योंकि आपका आवेदन बाहर निकलने वाला है), या यह GZIPOutputStream होगा जो IOException फेंकता है; बाकी के कंस्ट्रक्टरों के पास कोई अपवाद नहीं है, और ऐसी कोई अन्य परिस्थितियां नहीं हैं जो रनटाइम अपवाद का उत्पादन करने की संभावना रखते हैं।
जूल्स

5
@ जूल्स: हाँ, इन विशिष्ट धाराओं के लिए, वास्तव में। यह अच्छी आदतों के बारे में अधिक है।
टीजे क्राउडर

2
@PeterLawrey: मैं बुरी आदतों का उपयोग करने या धारा क्रियान्वयन पर निर्भर नहीं होने से बहुत असहमत हूं। :-) यह एक YAGNI / नहीं-YAGNI भेद नहीं है, यह उन पैटर्न के बारे में है जो विश्वसनीय कोड बनाते हैं।
टीजे क्राउडर

2
@PeterLawrey: भरोसा नहीं करने के बारे में ऊपर कुछ भी नहीं है java.io। कुछ धाराएँ - सामान्यीकरण, कुछ संसाधन - निर्माणकर्ताओं से फेंकते हैं। इसलिए यह सुनिश्चित करते हुए कि कई संसाधन व्यक्तिगत रूप से खोले जाते हैं, इसलिए यदि वे बाद के संसाधन फेंकता है, तो यह एक विश्वसनीय आदत है। यदि आप असहमत हैं, तो आप इसे ठीक नहीं करना चुन सकते हैं ।
टीजे क्राउडर

2
@PeterLawrey: तो आप समय-समय पर मामले के आधार पर किसी अपवाद के दस्तावेजीकरण के लिए कार्यान्वयन के स्रोत कोड को देखने के लिए समय लेने की वकालत करते हैं, और फिर कहते हैं, "ओह, ठीक है, यह वास्तव में फेंक नहीं है, इसलिए। .. "और टाइपिंग के कुछ पात्रों को बचा रहा है? हम कंपनी का हिस्सा है, बड़ा समय है। :-) इसके अलावा, मैंने अभी देखा, और यह सैद्धांतिक नहीं है: GZIPOutputStreamनिर्माणकर्ता स्ट्रीम को हेडर लिखता है। और इसलिए यह फेंक सकता है। तो अब स्थिति यह है कि क्या मुझे लगता है कि यह लिखने के बाद धारा को बंद करने की कोशिश करने के लिए परेशान करने लायक है । हाँ: मैंने इसे खोला है, मुझे कम से कम इसे बंद करने की कोशिश करनी चाहिए।
टीजे क्राउडर

12

आप बाहरी सबसे स्ट्रीम को बंद कर सकते हैं, वास्तव में आपको लिपटे सभी धाराओं को बनाए रखने की आवश्यकता नहीं है और आप जावा 7 ट्राइ-विथ-रिसोर्स का उपयोग कर सकते हैं।

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

यदि आप YAGNI की सदस्यता लेते हैं, या आप-नहीं-आने-जरूरत-यह करते हैं, तो आपको केवल उसी कोड को जोड़ना चाहिए जो आपको वास्तव में चाहिए। आपको वह कोड नहीं जोड़ना चाहिए जिसकी आप कल्पना करते हैं कि आपको आवश्यकता हो सकती है लेकिन वास्तव में कुछ भी उपयोगी नहीं है।

इस उदाहरण को लें और कल्पना करें कि क्या गलत हो सकता है अगर आपने ऐसा नहीं किया और इसका क्या प्रभाव होगा?

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

चलिए FileOutputStream से शुरू करते हैं जो openसभी वास्तविक कार्य करने के लिए कहता है।

/**
 * 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;

यदि फ़ाइल नहीं मिली है, तो बंद करने के लिए कोई अंतर्निहित संसाधन नहीं है, इसलिए इसे बंद करने से कोई फर्क नहीं पड़ेगा। यदि फ़ाइल मौजूद है, तो उसे FileNotFoundException को फेंकना चाहिए। इसलिए इस लाइन से संसाधन को बंद करने की कोशिश करने से कुछ हासिल नहीं होगा।

फ़ाइल को सफलतापूर्वक खोलने के लिए आपको फ़ाइल बंद करने का कारण है, लेकिन बाद में आपको एक त्रुटि मिलती है।

अगली धारा को देखते हैं GZIPOutputStream

कोड है जो एक अपवाद फेंक सकता है

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

यह फाइल का हैडर लिखता है। अब आपके लिए यह बहुत ही असामान्य हो जाएगा कि आप लिखने के लिए एक फ़ाइल खोलने में सक्षम हों, लेकिन इसके लिए 8 बाइट्स भी नहीं लिख पाएंगे, लेकिन यह कल्पना कर सकते हैं कि ऐसा हो सकता है और हम बाद में फ़ाइल को बंद नहीं करते हैं। यदि यह बंद न हो तो किसी फाइल का क्या होगा?

आपको कोई भी अप्रकाशित लिख नहीं मिलता है, उन्हें छोड़ दिया जाता है और इस मामले में, धारा में सफलतापूर्वक कोई बाइट्स नहीं लिखा जाता है जो इस बिंदु पर किसी भी तरह से बफर नहीं होता है। लेकिन एक फाइल जो बंद नहीं है वह हमेशा के लिए नहीं रहती है, इसके बजाय FileOutputStream के पास है

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

यदि आप किसी फ़ाइल को बिल्कुल भी बंद नहीं करते हैं, तो यह वैसे भी बंद हो जाती है, बस तुरंत नहीं (और जैसा मैंने कहा, डेटा जो एक बफर में छोड़ दिया गया है वह इस तरह से खो जाएगा, लेकिन इस बिंदु पर कोई भी नहीं है)

फ़ाइल को तुरंत बंद न करने का परिणाम क्या है? सामान्य परिस्थितियों में, आप संभावित रूप से कुछ डेटा खो देते हैं, और आप संभावित रूप से फ़ाइल डिस्क्रिप्टर से बाहर निकल जाते हैं। लेकिन अगर आपके पास एक ऐसी प्रणाली है जहाँ आप फाइलें बना सकते हैं, लेकिन आप उन्हें कुछ नहीं लिख सकते हैं, तो आपको बड़ी समस्या है। यानी यह कल्पना करना कठिन है कि आप बार-बार इस फाइल को बनाने की कोशिश कर रहे हैं कि आप असफल क्यों हो रहे हैं।

दोनों OutputStreamWriter और BufferedWriter अपने कंस्ट्रक्टर्स में IOException नहीं फेंकते हैं, इसलिए यह स्पष्ट नहीं है कि वे किस समस्या का कारण बनेंगे। बफ़र किए गए वैटर के मामले में, आपको एक आउटऑफ़मैरीऑरर मिल सकता है। इस मामले में यह तुरंत एक जीसी को ट्रिगर करेगा, जैसा कि हमने देखा है कि वैसे भी फाइल बंद हो जाएगी।


1
उन स्थितियों के लिए टीजे क्राउडर का जवाब देखें जहां यह विफल हो सकता है।
तैमूर

@TimK आप एक उदाहरण प्रदान कर सकते हैं कि फ़ाइल कहाँ बनाई गई है लेकिन स्ट्रीम बाद में विफल हो जाती है और परिणाम क्या होता है। विफलता का जोखिम बहुत कम है और प्रभाव तुच्छ है। जरूरत से ज्यादा जटिल बनाने की जरूरत नहीं है।
पीटर लॉरी

1
GZIPOutputStream(OutputStream)दस्तावेज़ IOExceptionऔर, स्रोत को देखते हुए, वास्तव में हेडर लिखते हैं। तो यह सैद्धांतिक नहीं है, कि कंस्ट्रक्टर फेंक सकता है। आपको लगता है कि इसे FileOutputStreamलिखने के बाद अंतर्निहित खुला छोड़ देना ठीक है। मैं नही।
टीजे क्राउडर

1
@TJCrowder कोई भी जो एक अनुभवी पेशेवर जावास्क्रिप्ट डेवलपर (और अन्य भाषाओं के अलावा) है, मैं अपनी टोपी उतारता हूं। मैं ऐसा नहीं कर सका। ;)
पीटर लॉरी

1
बस इसे फिर से देखने के लिए, दूसरा मुद्दा यह है कि यदि आप किसी फ़ाइल पर GZIPOutputStream का उपयोग कर रहे हैं और स्पष्ट रूप से कॉल समाप्त नहीं करते हैं, तो इसे बंद कार्यान्वयन कहा जाएगा। यह एक कोशिश में नहीं है ... अंत में अगर खत्म / फ्लश एक अपवाद फेंकता है तो अंतर्निहित फ़ाइल हैंडल कभी बंद नहीं होगा।
रोबर्ट_डिफ़ाल्को

6

यदि सभी धाराओं को त्वरित किया गया है, तो केवल बाहरी को बंद करना ठीक है।

Closeableइंटरफ़ेस पर प्रलेखन बताता है कि करीबी विधि:

इस स्ट्रीम को बंद कर देता है और इससे जुड़े किसी भी सिस्टम रिसोर्सेस को रिलीज़ करता है।

रिलीजिंग सिस्टम संसाधनों में क्लोजिंग स्ट्रीम शामिल हैं।

यह भी बताता है कि:

यदि धारा पहले से ही बंद है तो इस विधि को लागू करने से कोई प्रभाव नहीं पड़ता है।

इसलिए यदि आप उन्हें स्पष्ट रूप से बाद में बंद करते हैं, तो कुछ भी गलत नहीं होगा।


2
यह धाराओं के निर्माण में कोई त्रुटि नहीं मानता है, जो सूचीबद्ध लोगों के लिए सही हो सकता है या नहीं भी हो सकता है, लेकिन सामान्य रूप से विश्वसनीय नहीं है
टीजे क्राउडर

6

मैं try(...)सिंटैक्स (जावा 7) का उपयोग करना चाहता हूं , जैसे

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

4
जबकि मैं आपसे सहमत हूं, आप इस दृष्टिकोण के लाभ को उजागर करना चाहते हैं और इस बिंदु पर उत्तर दे सकते हैं कि क्या ओपी को बच्चे / आंतरिक धाराओं को बंद करने की आवश्यकता है
MadProgrammer

5

यह ठीक होगा यदि आप केवल अंतिम स्ट्रीम बंद करते हैं - करीबी कॉल अंतर्निहित धाराओं को भी भेजा जाएगा।


1
Grzegorz'sur के उत्तर पर टिप्पणी देखें।
टीजे क्राउडर

5

नहीं, सबसे ऊपरी स्तर Streamया readerयह सुनिश्चित करेगा कि सभी अंतर्निहित धाराएँ / पाठक बंद हैं।

अपने उच्चतम स्तर की धारा के close()विधि कार्यान्वयन की जाँच करें ।


हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.