প্রতিটি নেস্টেড আউটপুট স্ট্রিম এবং লেখককে আলাদাভাবে বন্ধ করা কি প্রয়োজনীয়?


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
মনে রাখবেন যে আপনার উদাহরণটিতে একটি ত্রুটি রয়েছে যা ডেটা ক্ষতি হতে পারে, কারণ আপনি স্ট্রিমগুলি যেভাবে খোলেন সেভাবে নয় not কোনও বন্ধ করার সময় অন্তর্নিহিত স্ট্রিমটিতে বাফার ডেটা BufferedWriterলেখার প্রয়োজন হতে পারে যা আপনার উদাহরণে ইতিমধ্যে বন্ধ রয়েছে। এই সমস্যাগুলি এড়ানো উত্তরগুলির মধ্যে দেখানো -সহ-সংস্থান পদ্ধতির পদ্ধতির আরেকটি সুবিধা ।
জো 23

উত্তর:


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) এর ভিতরে নেস্টেড কলগুলি দ্বারা নির্মিত যে কোনও প্রবাহটি উন্মুক্ত ছেড়ে দেবে। জেএলএস থেকে .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খোলা ছেড়ে দেবে । যেহেতু কিছু সংস্থার এমন কনস্ট্রাক্টর রয়েছে যেগুলি নিক্ষেপ করতে পারে এবং অন্যরা তা না করে, কেবল তাদের আলাদা করে তালিকাভুক্ত করা ভাল অভ্যাস।

আমরা এই প্রোগ্রামের সাথে সেই জেএলএস বিভাগটির আমাদের ব্যাখ্যাটি আবার যাচাই করতে পারি:

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

... যার আউটপুট রয়েছে:

উদাহরণ নির্মাণ করা হচ্ছে ner ইনারমোস্ট
উদাহরণ নির্মাণ $ মধ্যম
উদাহরণ নির্মাণ করা $ আউটারমোস্ট
ক্যাচ ব্লকে
অবশেষে অবরুদ্ধ
মূল শেষে

মনে রাখবেন যে সেখানে কোনও কল 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কলগুলি পাই :

উদাহরণ নির্মাণ করা হচ্ছে ner ইনারমোস্ট
উদাহরণ নির্মাণ $ মধ্যম
উদাহরণ নির্মাণ করা $ আউটারমোস্ট
উদাহরণ $ মধ্যম বন্ধ
উদাহরণ $ ইনারমোস্ট বন্ধ
উদাহরণ $ ইনারমোস্ট বন্ধ
ক্যাচ ব্লকে
অবশেষে অবরুদ্ধ
মূল শেষে

(হ্যাঁ, দুটি কল InnerMost#closeসঠিক; একটি হ'ল Middle, অন্যটি সংস্থান-ব্যবহারের মাধ্যমে))


7
স্ট্রিমগুলি নির্মাণের সময় ব্যতিক্রমগুলি ছুঁড়ে ফেলা হতে পারে তা লক্ষ করার জন্য +1, যদিও আমি লক্ষ করব যে বাস্তবে আপনি মেমরির বাইরে থাকা বা সমানভাবে গুরুতর কিছু পেতে চলেছেন (যে মুহুর্তে এটি সত্যিকার অর্থে কিছু যায় আসে না) যদি আপনি আপনার স্ট্রিমগুলি বন্ধ করে দেন, কারণ আপনার অ্যাপ্লিকেশনটি প্রস্থান হতে চলেছে), বা এটি GZIPOutputStream হবে যা একটি IOException ছোঁড়াবে; বাকী কনস্ট্রাক্টরের কোনও চেক ব্যতিক্রম নেই এবং অন্য কোনও পরিস্থিতি নেই যা রানটাইম ব্যতিক্রমের সম্ভাবনা রয়েছে।
জুলস

5
@ জুলস: হ্যাঁ, এই নির্দিষ্ট স্ট্রিমগুলির জন্য, সত্যই। এটি ভাল অভ্যাস সম্পর্কে আরও বেশি।
টিজে ক্রাউডার

2
@ পিটারলাউরে: খারাপ অভ্যাস ব্যবহার বা স্ট্রিম বাস্তবায়নের উপর নির্ভর না করে আমি দৃ strongly়ভাবে একমত নই। :-) এটি কোনও YAGNI / No-YAGNI পার্থক্য নয়, এটি নির্ভরযোগ্য কোডের জন্য তৈরি এমন নিদর্শনগুলির বিষয়ে।
টিজে ক্রাউডার

2
@ পিটারলাউরে: বিশ্বাস না করার উপরোক্ত কিছু নেই java.ioeither কিছু স্ট্রিম - সাধারণীকরণ, কিছু সংস্থান - নির্মাণকারীদের কাছ থেকে নিক্ষেপ। সুতরাং একাধিক সংস্থান পৃথকভাবে খোলার বিষয়টি নিশ্চিত করে যাতে কোনও পরবর্তী উত্স নিক্ষেপ করা কেবলমাত্র একটি ভাল অভ্যাস, আমার দৃষ্টিতে এগুলি নির্ভরযোগ্যভাবে বন্ধ করা যেতে পারে। আপনি যদি মতানৈক্য করেন তবে এটি না করার জন্য বেছে নিতে পারেন, ঠিক আছে।
টিজে ক্রাউডার

2
@ পিটারলাউরে: সুতরাং আপনি কেস-বাই-কেস ভিত্তিতে একটি ব্যতিক্রম দলিল করার জন্য কোনও বাস্তবায়নের উত্স কোডটি দেখার জন্য সময় দেওয়ার পক্ষে এবং তারপরে "ওহ, ভাল, এটি আসলে ছুঁড়ে না, তাই বলে। .. "এবং টাইপিংয়ের কয়েকটি অক্ষর সংরক্ষণ করছেন? আমরা সেখানে বড় অংশ। :-) তদ্ব্যতীত, আমি স্রেফ দেখেছি এবং এটি তাত্ত্বিক নয়: GZIPOutputStreamএর নির্মাতা স্ট্রিমটিতে একটি শিরোনাম লিখেছেন। এবং তাই এটি নিক্ষেপ করতে পারেন। সুতরাং এখন অবস্থানটি আমি মনে করি লেখার পরে প্রবাহ বন্ধ করার চেষ্টা করা বিরক্তিকর বলে মনে করি । হ্যাঁ: আমি এটি খুললাম, কমপক্ষে এটি বন্ধ করার চেষ্টা করা উচিত।
টিজে ক্রাউডার

12

আপনি বহিরাগত সর্বাধিক স্ট্রিমটি বন্ধ করতে পারেন, বাস্তবে আপনার জড়িত সমস্ত স্ট্রিম ধরে রাখতে হবে না এবং আপনি জাভা use টি ব্যবহার করে রিসোর্স ব্যবহার করতে পারেন।

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

আপনি যদি YAGNI, বা আপনি-এটি-দরকার-এর সাবস্ক্রাইব করে থাকেন তবে আপনার কেবলমাত্র প্রয়োজন কোডটি যুক্ত করা উচিত actually আপনার প্রয়োজন হতে পারে এমন কল্পনাটি কোড যুক্ত করা উচিত নয় তবে বাস্তবে কার্যকর কোনও কাজ করে না।

এই উদাহরণটি ধরুন এবং কল্পনা করুন যে আপনি যদি এটি না করেন এবং সম্ভবত প্রভাব কী হতে পারে তবে কী ভুল হতে পারে?

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

ফাইলআউটপুট স্ট্রিম দিয়ে শুরু করা যাক যা 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;

যদি ফাইলটি খুঁজে পাওয়া যায় না, তবে বন্ধ করার কোনও অন্তর্নিহিত সংস্থান নেই, সুতরাং এটি বন্ধ করে কোনও তাত্পর্য তৈরি করবে না। যদি ফাইলটি বিদ্যমান থাকে তবে এটি ফাইলনটফাউন্ডএক্সসেপশন নিক্ষেপ করা উচিত। সুতরাং একা এই লাইন থেকে সংস্থানটি বন্ধ করার চেষ্টা করে লাভ করার কিছু নেই।

আপনার ফাইলটি বন্ধ করার কারণটি হ'ল ফাইলটি সফলভাবে খোলা থাকলেও পরে আপনি একটি ত্রুটি পান।

পরের স্ট্রিমটি দেখতে দিন 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 বাইট এমনকি লিখতেও সক্ষম হবেন না, তবে কল্পনা করুন যে এটি ঘটতে পারে এবং আমরা পরে ফাইলটি বন্ধ করি না। ফাইলটি বন্ধ না হলে কী হবে?

আপনি কোনও অপ্রকাশিত লেখাগুলি পান না, সেগুলি বাতিল করে দেওয়া হয় এবং এই ক্ষেত্রে স্ট্রিমের কাছে কোনও সফলভাবে লিখিত বাইট নেই যা এই মুহুর্তে বাফার হয় না। তবে যে ফাইলটি বন্ধ নেই তা চিরকালের জন্য বাঁচে না, পরিবর্তে ফাইলআউটপুট স্ট্রিম রয়েছে

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

আপনি যদি কোনও ফাইল বন্ধ না করেন তবে এটি তাত্ক্ষণিকভাবেই বন্ধ হয়ে যায় (এবং আমি যেমন বলেছি, বাফারে থাকা ডেটা এইভাবে হারিয়ে যাবে, তবে এই মুহুর্তে কিছুই নেই)

অবিলম্বে ফাইলটি বন্ধ না করার পরিণতি কী? সাধারণ পরিস্থিতিতে, আপনি সম্ভাব্যভাবে কিছু ডেটা হারাবেন এবং আপনার সম্ভাব্য ফাইল বিবরণীর বাইরে চলে যায়। তবে আপনার যদি এমন একটি সিস্টেম থাকে যেখানে আপনি ফাইল তৈরি করতে পারেন তবে আপনি সেগুলিতে কিছুই লিখতে পারবেন না, আপনার একটি বড় সমস্যা রয়েছে। যেমন আপনি ব্যর্থ হচ্ছেন সত্ত্বেও কেন আপনি বারবার এই ফাইলটি তৈরি করার চেষ্টা করছেন তা কল্পনা করা শক্ত।

উভয় আউটপুট স্ট্রিমাইটার এবং বাফারড রাইটারই তাদের নির্মাতাদের মধ্যে আইওএক্সেপশন নিক্ষেপ করে না, সুতরাং তারা কী সমস্যা করবে তা পরিষ্কার নয়। বাফারড্রিটারের ক্ষেত্রে আপনি একটি আউটআফমিউরিওর পেতে পারেন। এই ক্ষেত্রে এটি অবিলম্বে একটি জিসিকে ট্রিগার করবে, যা আমরা দেখেছি যেভাবেই ফাইলটি বন্ধ হয়ে যাবে।


1
যে পরিস্থিতিতে এটি ব্যর্থ হতে পারে তার জন্য টিজে ক্রডারের উত্তর দেখুন।
টিমকে

@ টিমক আপনি ফাইলটি কোথায় তৈরি হয়েছে তার একটি উদাহরণ সরবরাহ করতে পারেন তবে পরে স্ট্রিমটি ব্যর্থ হয় এবং এর পরিণতি কী। ব্যর্থতার ঝুঁকি অত্যন্ত কম এবং এর প্রভাব তুচ্ছ। এটি যতটা প্রয়োজন তত বেশি জটিল করার দরকার নেই।
পিটার লরি

1
GZIPOutputStream(OutputStream)দস্তাবেজগুলি IOExceptionএবং উত্সের দিকে তাকিয়ে আসলে একটি শিরোনাম লিখে। সুতরাং এটি তাত্ত্বিক নয়, যে কনস্ট্রাক্টর নিক্ষেপ করতে পারে। আপনার মনে হতে FileOutputStreamপারে এটি ছুঁড়ে দেওয়ার পরে অন্তর্নিহিতটি ছেড়ে দেওয়া ঠিক আছে । আমি না।
টিজে ক্রাউডার

1
@ টিজে ক্রাউডার যে কেউ অভিজ্ঞ পেশাদার জাভাস্ক্রিপ্ট বিকাশকারী (এবং অন্যান্য ভাষা ছাড়াও) আমি আমার টুপিটি বন্ধ করে দিই। আমি এটা করতে পারি না। ;)
পিটার ল্যারি

1
কেবল এটি পুনর্বিবেচনার জন্য, অন্য সমস্যাটি হ'ল যদি আপনি কোনও ফাইলটিতে একটি জিজেআইপিআউটপুট স্ট্রিম ব্যবহার করেন এবং স্পষ্টভাবে ফিনিস কল না করেন তবে এটির কাছাকাছি বাস্তবায়নে বলা হবে। এটি চেষ্টা করার দরকার নেই ... অবশেষে তাই যদি সমাপ্তি / ফ্লাশ কোনও ব্যতিক্রম ছুঁড়ে দেয় তবে অন্তর্নিহিত ফাইল হ্যান্ডেলটি কখনও বন্ধ হবে না।
রবার্ট_ডিফলক

6

যদি সমস্ত স্ট্রিম তাত্ক্ষণিকভাবে চালিত হয় তবে কেবল বাহ্যিকটি বন্ধ করা ঠিক fine

Closeableইন্টারফেসের ডকুমেন্টেশনে উল্লেখ করা হয়েছে যে নিকটতম পদ্ধতি:

এই স্ট্রিমটি বন্ধ করে এবং এর সাথে যুক্ত যেকোনও সিস্টেম সংস্থান প্রকাশ করে।

রিলিজিং সিস্টেম রিসোর্সে ক্লোমিং স্ট্রিম অন্তর্ভুক্ত রয়েছে।

এতে আরও বলা হয়েছে:

যদি স্ট্রিমটি ইতিমধ্যে বন্ধ হয়ে যায় তবে এই পদ্ধতিটি আহ্বানের কোনও প্রভাব নেই।

সুতরাং আপনি যদি পরে সেগুলি স্পষ্টভাবে বন্ধ করেন তবে কিছুই ভুল হবে না।


2
এটি স্ট্রিমগুলি নির্মাণে কোনও ত্রুটি অনুমান করে, যা তালিকাভুক্তদের ক্ষেত্রে সত্য হতে পারে বা নাও হতে পারে তবে সাধারণভাবে নির্ভরযোগ্যভাবে সত্য নয়
টিজে ক্রাউডার

6

আমি বরং try(...)বাক্য গঠন (জাভা 7) ব্যবহার করতাম , যেমন

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

4
যদিও আমি আপনার সাথে একমত, আপনি এই পদ্ধতির সুবিধা হাইলাইট এবং বিন্দু উত্তর দিতে চাইতে পারেন যদি ওপি শিশু / ভেতরের স্ট্রিম বন্ধ করতে দরকার
MadProgrammer

5

আপনি কেবলমাত্র শেষ স্ট্রিমটি বন্ধ করলে এটি ঠিক থাকবে - নিকটে কলটি অন্তর্নিহিত স্ট্রিমগুলিতেও প্রেরণ করা হবে।


1
গ্রজেগোর্স এর উত্তর সম্পর্কে মন্তব্য দেখুন।
টিজে ক্রাউডার

5

না, শীর্ষস্থানীয় স্তর Streamবা readerএটি নিশ্চিত করবে যে সমস্ত অন্তর্নিহিত স্ট্রিম / পাঠক বন্ধ রয়েছে।

আপনার শীর্ষ স্তরের স্ট্রিমের close()পদ্ধতি বাস্তবায়ন পরীক্ষা করে দেখুন ।


আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.