面試官:IO 操作必須要手動關閉嗎?關閉流方法是否有順序?

来源:https://www.cnblogs.com/javastack/archive/2022/06/17/16386076.html
-Advertisement-
Play Games

前幾天看了一篇文章,自己動手試了下,發現有些不一樣結論,作博客記錄下,本文主要研究兩個問題: 包裝流的close方法是否會自動關閉被包裝的流? 關閉流方法是否有順序? 包裝流的close方法是否會自動關閉被包裝的流? 平時我們使用輸入流和輸出流一般都會使用buffer包裝一下,直接看下麵代碼(這個代 ...


前幾天看了一篇文章,自己動手試了下,發現有些不一樣結論,作博客記錄下,本文主要研究兩個問題:

  • 包裝流的close方法是否會自動關閉被包裝的流?
  • 關閉流方法是否有順序?

包裝流的close方法是否會自動關閉被包裝的流?

平時我們使用輸入流和輸出流一般都會使用buffer包裝一下,直接看下麵代碼(這個代碼運行正常,不會報錯)

import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;


public class IOTest {

    public static void main(String[] args) throws IOException {

         FileOutputStream fileOutputStream = new FileOutputStream("c:\\a.txt");
         BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream);

         bufferedOutputStream.write("test write something".getBytes());
         bufferedOutputStream.flush();

         //從包裝流中關閉流
         bufferedOutputStream.close();
    }

}

下麵我們來研究下這段代碼的bufferedOutputStream.close();方法是否調用了fileOutputStream.close();

先看BufferedOutputStream源代碼:

public class BufferedOutputStream extends FilterOutputStream { ...

可以看到它繼承FilterOutputStream,並且沒有重寫close方法,所以直接看FilterOutputStream的源代碼:

public void close() throws IOException {
    try {
      flush();
    } catch (IOException ignored) {
    }
    out.close();
}

跟蹤out(FilterOutputStream中):

  protected OutputStream out;

  public FilterOutputStream(OutputStream out) {
        this.out = out;
  }

再看看BufferedOutputStream中:

public BufferedOutputStream(OutputStream out) {
    this(out, 8192);
}

public BufferedOutputStream(OutputStream out, int size) {
    super(out);
    if (size <= 0) {
        throw new IllegalArgumentException("Buffer size <= 0");
    }
    buf = new byte[size];
}

可以看到BufferedOutputStream調用super(out);,也就是說,out.close();調用的是通過BufferedOutputStream傳入的被包裝的流,這裡就是FileOutputStream

我們在看看其他類似的,比如BufferedWriter的源代碼:

public void close() throws IOException {
    synchronized (lock) {
        if (out == null) {
            return;
        }
        try {
            flushBuffer();
        } finally {
            out.close();
            out = null;
            cb = null;
        }
    }
}

通過觀察各種流的源代碼,可得結論:包裝的流都會自動調用被包裝的流的關閉方法,無需自己調用。

關閉流方法是否有順序?

由上面的結論,就會產生一個問題:如果手動關閉被包裝流會怎麼樣,這個關閉流有順序嗎?而實際上我們習慣都是兩個流都關閉的。

首先我們來做一個簡單的實驗,基於第一個問題的代碼上增加手動增加關閉流的代碼,那麼就有兩種順序:

1.先關閉被包裝流(正常沒異常拋出)

import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;


public class IOTest {

    public static void main(String[] args) throws IOException {

         FileOutputStream fileOutputStream = new FileOutputStream("c:\\a.txt");
         BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream);

         bufferedOutputStream.write("test write something".getBytes());
         bufferedOutputStream.flush();

         fileOutputStream.close();//先關閉被包裝流
         bufferedOutputStream.close();
    }

}

2.先關閉包裝流(正常沒異常拋出)

import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;


public class IOTest {

    public static void main(String[] args) throws IOException {

         FileOutputStream fileOutputStream = new FileOutputStream("c:\\a.txt");
         BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream);

         bufferedOutputStream.write("test write something".getBytes());
         bufferedOutputStream.flush();


         bufferedOutputStream.close();//先關閉包裝流
         fileOutputStream.close();

    }

}

上述兩種寫法都沒有問題,我們已經知道bufferedOutputStream.close();會自動調用fileOutputStream.close();方法,那麼這個方法是怎麼執行的呢?我們又看看FileOutputStream的源碼:

public void close() throws IOException {
    synchronized (closeLock) {
        if (closed) {
            return;
        }
        closed = true;
    }

...

可以看出它採用同步鎖,而且使用了關閉標記,如果已經關閉了則不會再次操作,所以多次調用不會出現問題。

如果沒有看過參考文章,我可能就會斷下結論,關閉流不需要考慮順序

我們看下下麵的代碼(修改自參考文章):

import java.io.BufferedWriter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;

public class IOTest {

    public static void main(String[] args) throws IOException {

        FileOutputStream fos = new FileOutputStream("c:\\a.txt");
        OutputStreamWriter osw = new OutputStreamWriter(fos, "UTF-8");
        BufferedWriter bw = new BufferedWriter(osw);
        bw.write("java IO close test");

        // 從內帶外順序順序會報異常
        fos.close();
        osw.close();
        bw.close();

    }

}

會拋出Stream closed的IO異常:

Exception in thread "main" java.io.IOException: Stream closed
    at sun.nio.cs.StreamEncoder.ensureOpen(StreamEncoder.java:45)
    at sun.nio.cs.StreamEncoder.write(StreamEncoder.java:118)
    at java.io.OutputStreamWriter.write(OutputStreamWriter.java:207)
    at java.io.BufferedWriter.flushBuffer(BufferedWriter.java:129)
    at java.io.BufferedWriter.close(BufferedWriter.java:264)
    at IOTest.main(IOTest.java:18)

而如果把bw.close();放在第一,其他順序任意,即修改成下麵兩種:

bw.close();
osw.close();
fos.close();
bw.close();
fos.close();
osw.close();

都不會報錯,這是為什麼呢,我們立即看看BufferedWriter的close源碼:

public void close() throws IOException {
    synchronized (lock) {
        if (out == null) {
            return;
        }
        try {
            flushBuffer();
        } finally {
            out.close();
            out = null;
            cb = null;
        }
    }
}

裡面調用了flushBuffer()方法,也是拋異常中的錯誤方法:

void flushBuffer() throws IOException {
    synchronized (lock) {
        ensureOpen();
        if (nextChar == 0)
            return;
        out.write(cb, 0, nextChar);
        nextChar = 0;
    }
}

可以看到很大的一行

out.write(cb, 0, nextChar);

這行如果在流關閉後執行就會拋IO異常,有時候我們會寫成:

fos.close();
fos = null;
osw.close();
osw = null;
bw.close();
bw = null;

這樣也會拋異常,不過是由於flushBuffer()ensureOpen()拋的,可從源碼中看出:

private void ensureOpen() throws IOException {
    if (out == null)
        throw new IOException("Stream closed");
}


void flushBuffer() throws IOException {
    synchronized (lock) {
        ensureOpen();
        if (nextChar == 0)
            return;
        out.write(cb, 0, nextChar);
        nextChar = 0;
    }
}

如何防止這種情況?

直接寫下麵這種形式就可以:

bw.close();
bw = null;

結論:一個流上的close方法可以多次調用,理論上關閉流不需要考慮順序,但有時候關閉方法中調用了write等方法時會拋異常。


由上述的兩個結論可以得出下麵的建議:

關閉流只需要關閉最外層的包裝流,其他流會自動調用關閉,這樣可以保證不會拋異常。如:

bw.close();
//下麵三個無順序
osw = null;
fos = null;
bw = null;

註意的是,有些方法中close方法除了調用被包裝流的close方法外還會把包裝流置為null,方便JVM回收。bw.close()中的:

 public void close() throws IOException {
        synchronized (lock) {
            if (out == null) {
                return;
            }
            try {
                flushBuffer();
            } finally {
                out.close();
                out = null;
                cb = null;
            }
        }
    }

finally中就有把out置為null的代碼,所以有時候不需要自己手動置為null。(個人建議還是寫一下,不差多少執行時間)

來源:blog.csdn.net/maxwell_nc/article/details/49151005

近期熱文推薦:

1.1,000+ 道 Java面試題及答案整理(2022最新版)

2.勁爆!Java 協程要來了。。。

3.Spring Boot 2.x 教程,太全了!

4.別再寫滿屏的爆爆爆炸類了,試試裝飾器模式,這才是優雅的方式!!

5.《Java開發手冊(嵩山版)》最新發佈,速速下載!

覺得不錯,別忘了隨手點贊+轉發哦!


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1、前言 作為嵌入式軟體開發,可能經常會使用命令行或者顯示屏等設備實現人機交互的功能,功能中通常情況都包含 UI 菜單設計;很多開發人員都會有自己的菜單框架模塊,防止重覆造輪子,網上有很多這種菜單框架的代碼,但是大多耦合性太強,無法獨立出來適配不同的菜單設計。 本文介紹一個降低了耦合性,完全獨立的菜 ...
  • 目錄 一.簡介 二.效果演示 三.源碼下載 四.猜你喜歡 零基礎 OpenGL (ES) 學習路線推薦 : OpenGL (ES) 學習目錄 >> OpenGL ES 基礎 零基礎 OpenGL (ES) 學習路線推薦 : OpenGL (ES) 學習目錄 >> OpenGL ES 轉場 零基礎 O ...
  • 分類方式 按參數分: 有參構造(預設構造) & 無參構造 按類型分: 普通構造 & 拷貝構造 調用方式 括弧法 顯示法 隱式轉換法 PS:下方所有文本均以此代碼為基礎 1 class Person { 2 public: 3 //無參構造函數 4 Person() { 5 std::cout << ...
  • 引言:今天工作遇到了一個需要按行讀取txt文件數據的需求,查詢了一下getline()函數,發現這竟然是一個C++的標準庫函數,而且設計的很好,特地做一下記錄。getline本質是一個定界流輸入截取函數,預設是換行符‘/n’ 個人技術博客(文章整理+源碼): https://zobolblog.gi ...
  • 大佬的理解->《IO流和File》 1、File類 File類是IO包中唯一代表磁碟文件本身的對象,File類定義了一些與平臺無關的方法來操作文件。通過調用File類提供的各種方法,能夠完成創建、刪除文件、重命名文件、判斷文件的讀寫許可權許可權是否存在、設置和查詢文件的最近修改時間等操作。 ​ File ...
  • 前言 大部分情況下,地理繪圖可使用 Arcgis 等工具實現。但正版的 Arcgis 並非所有人可以承受。本文基於 Python 的 cartopy 和 matplotlib 等庫,為地理空間繪圖的代碼實現提供參考。 所有所需庫如下: gma、cartopy、matplotlib、numpy 更多內 ...
  • 1 問題的提出 對於給定的數據集 \(D = \{(x_1,y_1),(x_2,y_2),\cdots,(x_m,y_m)\}\),線性回歸 (linear regression) 試圖學得一個線性模型以儘可能準確地預測是指輸出標記. 2 原理 設給定的數據集 \(D = \{(x_i,y_i)\} ...
  • 基礎數據準備 基礎數據是通過爬蟲獲取到。 以下是從第一期03年雙色球開獎號到今天的所有數據整理,截止目前一共2549期,balls.txt 文件內容如下 Python 代碼實現 分析數據特征和數據處理方式選擇 python學習交流Q群:906715085### #導入Counter from col ...
一周排行
    -Advertisement-
    Play Games
  • 1、預覽地址:http://139.155.137.144:9012 2、qq群:801913255 一、前言 隨著網路的發展,企業對於信息系統數據的保密工作愈發重視,不同身份、角色對於數據的訪問許可權都應該大相徑庭。 列如 1、不同登錄人員對一個數據列表的可見度是不一樣的,如數據列、數據行、數據按鈕 ...
  • 前言 上一篇文章寫瞭如何使用RabbitMQ做個簡單的發送郵件項目,然後評論也是比較多,也是準備去學習一下如何確保RabbitMQ的消息可靠性,但是由於時間原因,先來說說設計模式中的簡單工廠模式吧! 在瞭解簡單工廠模式之前,我們要知道C#是一款面向對象的高級程式語言。它有3大特性,封裝、繼承、多態。 ...
  • Nodify學習 一:介紹與使用 - 可樂_加冰 - 博客園 (cnblogs.com) Nodify學習 二:添加節點 - 可樂_加冰 - 博客園 (cnblogs.com) 介紹 Nodify是一個WPF基於節點的編輯器控制項,其中包含一系列節點、連接和連接器組件,旨在簡化構建基於節點的工具的過程 ...
  • 創建一個webapi項目做測試使用。 創建新控制器,搭建一個基礎框架,包括獲取當天日期、wiki的請求地址等 創建一個Http請求幫助類以及方法,用於獲取指定URL的信息 使用http請求訪問指定url,先運行一下,看看返回的內容。內容如圖右邊所示,實際上是一個Json數據。我們主要解析 大事記 部 ...
  • 最近在不少自媒體上看到有關.NET與C#的資訊與評價,感覺大家對.NET與C#還是不太瞭解,尤其是對2016年6月發佈的跨平臺.NET Core 1.0,更是知之甚少。在考慮一番之後,還是決定寫點東西總結一下,也回顧一下.NET的發展歷史。 首先,你沒看錯,.NET是跨平臺的,可以在Windows、 ...
  • Nodify學習 一:介紹與使用 - 可樂_加冰 - 博客園 (cnblogs.com) Nodify學習 二:添加節點 - 可樂_加冰 - 博客園 (cnblogs.com) 添加節點(nodes) 通過上一篇我們已經創建好了編輯器實例現在我們為編輯器添加一個節點 添加model和viewmode ...
  • 前言 資料庫併發,數據審計和軟刪除一直是數據持久化方面的經典問題。早些時候,這些工作需要手寫複雜的SQL或者通過存儲過程和觸發器實現。手寫複雜SQL對軟體可維護性構成了相當大的挑戰,隨著SQL字數的變多,用到的嵌套和複雜語法增加,可讀性和可維護性的難度是幾何級暴漲。因此如何在實現功能的同時控制這些S ...
  • 類型檢查和轉換:當你需要檢查對象是否為特定類型,並且希望在同一時間內將其轉換為那個類型時,模式匹配提供了一種更簡潔的方式來完成這一任務,避免了使用傳統的as和is操作符後還需要進行額外的null檢查。 複雜條件邏輯:在處理複雜的條件邏輯時,特別是涉及到多個條件和類型的情況下,使用模式匹配可以使代碼更 ...
  • 在日常開發中,我們經常需要和文件打交道,特別是桌面開發,有時候就會需要載入大批量的文件,而且可能還會存在部分文件缺失的情況,那麼如何才能快速的判斷文件是否存在呢?如果處理不當的,且文件數量比較多的時候,可能會造成卡頓等情況,進而影響程式的使用體驗。今天就以一個簡單的小例子,簡述兩種不同的判斷文件是否... ...
  • 前言 資料庫併發,數據審計和軟刪除一直是數據持久化方面的經典問題。早些時候,這些工作需要手寫複雜的SQL或者通過存儲過程和觸發器實現。手寫複雜SQL對軟體可維護性構成了相當大的挑戰,隨著SQL字數的變多,用到的嵌套和複雜語法增加,可讀性和可維護性的難度是幾何級暴漲。因此如何在實現功能的同時控制這些S ...