الطريقة الصحيحة لإغلاق التدفقات والكتاب المتداخلين في Java [نسخة مكررة]

StackOverflow https://stackoverflow.com/questions/884007

  •  22-08-2019
  •  | 
  •  

سؤال

هذا السؤال لديه بالفعل إجابة هنا:

ملحوظة: يعود تاريخ هذا السؤال ومعظم إجاباته إلى ما قبل إصدار Java 7.يوفر جافا 7 إدارة الموارد التلقائية وظيفة للقيام بذلك بسهولة.إذا كنت تستخدم Java 7 أو إصدارًا أحدث، فيجب عليك التقدم إلى إجابة روس جونسون.


ما هي الطريقة الأفضل والأكثر شمولاً لإغلاق التدفقات المتداخلة في Java؟على سبيل المثال، النظر في الإعداد:

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

أدرك أن عملية الإغلاق تحتاج إلى التأمين (ربما باستخدام شرط نهائي).ما أتساءل عنه هو، هل من الضروري التأكد بشكل صريح من إغلاق التدفقات المتداخلة، أم يكفي التأكد فقط من إغلاق التدفق الخارجي (oos)؟

شيء واحد لاحظته، على الأقل عند التعامل مع هذا المثال المحدد، هو أن التدفقات الداخلية تبدو وكأنها ترمي FileNotFoundExceptions فقط.والذي يبدو أنه يعني أنه لا توجد حاجة من الناحية الفنية للقلق بشأن إغلاقها في حالة فشلها.

وإليك ما كتبه أحد الزملاء:


من الناحية الفنية ، إذا تم تنفيذها بشكل صحيح ، فيجب أن يكون إغلاق الدفق الخارجي (OOS) كافيًا.لكن التنفيذ يبدو معيبًا.

مثال:يرث BufferedOutputStream دالة Close() من FilterOutputStream، والتي تعرفه على النحو التالي:

 155       public void close() throws IOException {
 156           try {
 157             flush();
 158           } catch (IOException ignored) {
 159           }
 160           out.close();
 161       }

ومع ذلك ، إذا كان Flush () يلقي استثناء وقت التشغيل لسبب ما ، فلن يتم استدعاء Out.Close ().لذلك يبدو "أكثر أمانًا" (ولكن قبيح) للقلق في الغالب بشأن إغلاق FOS ، وهو ما يبقي الملف مفتوحًا.


ما الذي يعتبر أفضل نهج عملي، عندما تحتاج إلى التأكد تمامًا، لإغلاق التدفقات المتداخلة؟

وهل هناك أي مستندات Java/Sun رسمية تتعامل مع هذا الأمر بتفاصيل دقيقة؟

هل كانت مفيدة؟

المحلول

أفعل عادة ما يلي.أولاً، حدد فئة تعتمد على أسلوب القالب للتعامل مع فوضى المحاولة/الالتقاط

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

لاحظ الاستثناء "المعلق" - وهذا يعتني بالحالة التي يكون فيها الاستثناء الذي تم طرحه أثناء الإغلاق قد يخفي استثناءً قد نهتم به حقًا.

يحاول أخيرًا الإغلاق من الخارج لأي دفق مزين أولاً، لذلك إذا كان لديك BufferedWriter يغلف FileWriter، فإننا نحاول إغلاق BuffereredWriter أولاً، وإذا فشل ذلك، فلا نزال نحاول إغلاق FileWriter نفسه.(لاحظ أن تعريف المكالمات القابلة للغلق هو Close() لتجاهل المكالمة إذا كان الدفق مغلقًا بالفعل)

يمكنك استخدام الفئة أعلاه على النحو التالي:

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
}

باستخدام هذا الأسلوب، لا داعي للقلق أبدًا بشأن محاولة/التقاط/أخيرًا للتعامل مع إغلاق الملفات مرة أخرى.

إذا كان هذا ثقيلًا جدًا بالنسبة لاستخدامك، فكر على الأقل في اتباع أسلوب المحاولة/الالتقاط وأسلوب المتغير "المعلق" الذي يستخدمه.

نصائح أخرى

عند إغلاق التدفقات المتسلسلة، ما عليك سوى إغلاق التدفق الخارجي.سيتم نشر أي أخطاء في السلسلة وسيتم اكتشافها.

تشير إلى تدفقات الإدخال/الإخراج لجافا للتفاصيل.

لمعالجة هذه المسألة

ومع ذلك، إذا ألقى Flush() استثناءً في وقت التشغيل لسبب ما، فلن يتم استدعاء out.Close() أبدًا.

هذا ليس صحيحا.بعد التقاط هذا الاستثناء وتجاهله، سيتم استئناف التنفيذ بعد كتلة الالتقاط و out.close() سيتم تنفيذ البيان

زميلك يقدم نقطة جيدة حول مدة العرضاستثناء.إذا كنت بحاجة ماسة إلى إغلاق الدفق، فيمكنك دائمًا محاولة إغلاق كل واحد على حدة، من الخارج إلى الداخل، والتوقف عند الاستثناء الأول.

في عصر جافا 7، حاول مع الموارد هو بالتأكيد الطريق للذهاب.كما ذكرنا في العديد من الإجابات السابقة، ينتشر طلب الإغلاق من التيار الخارجي إلى التيار الأعمق.لذلك إغلاق واحد هو كل ما هو مطلوب.

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

ولكن هناك مشكلة مع هذا النمط.لا تكون أداة Try-with-resources على علم بـ FileInputStream الداخلي، لذلك إذا قام مُنشئ ObjectInputStream بطرح استثناء، فلن يتم إغلاق FileInputStream أبدًا (حتى يصل جامع البيانات المهملة إليه).الحل هو...

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

هذه ليست أنيقة، ولكنها أكثر قوة.يعتمد ما إذا كانت هذه مشكلة بالفعل على الاستثناءات التي يمكن طرحها أثناء إنشاء الكائن (الكائنات) الخارجي.يمكن لـ ObjectInputStream طرح IOException والذي قد تتم معالجته بواسطة تطبيق دون إنهائه.تقوم العديد من فئات الدفق بطرح استثناءات غير محددة فقط، مما قد يؤدي إلى إنهاء التطبيق.

من الممارسات الجيدة استخدام Apache Commons للتعامل مع الكائنات ذات الصلة بالإدخال والإخراج.

في ال finally استخدام جملة IOUtils

IOUtils.CloseQuietly(bWriter);IOUtils.CloseQuietly(oWritter);

مقتطف الكود أدناه.

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

يثير الزميل نقطة مثيرة للاهتمام، وهناك أسباب للجدال في كلا الاتجاهين.

شخصيا، سأتجاهل RuntimeException, لأن الاستثناء الذي لم يتم التحقق منه يدل على وجود خطأ في البرنامج.إذا كان البرنامج غير صحيح، قم بإصلاحه.لا يمكنك "التعامل" مع برنامج سيئ في وقت التشغيل.

هذا سؤال محرج بشكل مدهش.(حتى على افتراض acquire; try { use; } finally { release; } الرمز صحيح.)

إذا فشل بناء الديكور، فلن تقوم بإغلاق التدفق الأساسي.لذلك أنت بحاجة إلى إغلاق الدفق الأساسي بشكل صريح، سواء في النهاية بعد الاستخدام أو، وهو أمر أكثر صعوبة بعد تسليم المورد بنجاح إلى مصمم الديكور).

إذا تسبب استثناء في فشل التنفيذ، فهل تريد حقًا إجراء عملية التدفق؟

بعض مصممي الديكور لديهم بالفعل موارد بأنفسهم.تنفيذ الشمس الحالي ل ZipInputStream على سبيل المثال تم تخصيص ذاكرة كومة غير Java.

لقد تم الادعاء بأن (IIRC) ثلثي استخدامات الموارد في مكتبة Java يتم تنفيذها بطريقة غير صحيحة بشكل واضح.

بينما BufferedOutputStream يغلق حتى على IOException من flush, BufferedWriter يغلق بشكل صحيح.

نصيحتي:أغلق الموارد بشكل مباشر قدر الإمكان ولا تسمح لها بتلويث تعليمات برمجية أخرى.OTOH، يمكنك قضاء الكثير من الوقت في هذه المسألة - إذا OutOfMemoryError من الجيد أن تتصرف بشكل جيد، ولكن من المحتمل أن تكون الجوانب الأخرى لبرنامجك ذات أولوية أعلى ومن المحتمل أن يكون رمز المكتبة معطلاً في هذا الموقف على أي حال.ولكنني كنت أكتب دائما:

final FileOutputStream rawOut = new FileOutputStream(file);
try {
    OutputStream out = new BufferedOutputStream(rawOut);
    ... write stuff out ...
    out.flush();
} finally {
    rawOut.close();
}

(ينظر:لا صيد!)

وربما استخدم مصطلح التنفيذ حول.

جافا سي 7 حاول مع الموارد لا يبدو أن يتم ذكرها.إنه يلغي الحاجة إلى الإغلاق بشكل صريح تمامًا، وأنا أحب الفكرة تمامًا.

لسوء الحظ، بالنسبة لتطوير Android، يصبح هذا الحل متاحًا فقط باستخدام Android Studio (على ما أظن) و استهداف كيت كات وما فوق.

كما لا يتعين عليك إغلاق كافة التدفقات المتداخلة

افحص هذاhttp://ckarthik17.blogspot.com/2011/02/closing-nested-streams.html

أستخدم لإغلاق التدفقات مثل هذا، دون تداخل محاولة الالتقاط أخيرًا كتل

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

تتضمن مستندات JavaDocs الخاصة بشركة Sun RuntimeExceptions في وثائقهم، كما هو موضح بواسطة InputStream's قراءة (بايت []، كثافة العمليات، كثافة العمليات) طريقة؛تم توثيقه على أنه رمي NullPointerException وIndexOutOfBoundsException.

تصفيةOutputStream دافق () تم توثيقه فقط على أنه رمي IOException، وبالتالي فهو لا يرمي أي شيء فعليًا RuntimeExceptionس.من المرجح أن يتم لف أي شيء يمكن رميه في ملف IIOException.

لا يزال بإمكانه رمي Error, ولكن ليس هناك الكثير مما يمكنك فعله حيال ذلك؛توصي صن بعدم محاولة الإمساك بهم.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top