成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

為什么必須手動關閉IO流,不能坐等GC回收?

存儲 數據管理
如果對未關閉流的文件進行讀寫操作,可能就會報錯,告訴你這個文件被某個進程占用。如果不手動釋放資源,隨著資源占有量逐漸增多,垃圾會越來越多,最終可能導致系統無法存儲其他的資源,甚至會出現系統崩潰。

一、問題回溯

在項目的開發過程中,當我們對文件進行讀寫操作時,不知道大家有沒有碰到這樣的問題。

有的同學在做一個讀取臨時文件數據的工作,當讀完文件內容,準備將其刪除的時候,有時候會正常,但有時候會提示:操作無法完成,因為文件已在 Java? Platform SE binary 中打開,編譯器也會提示:Resource leak: 'xxxx' is never closed。

樣例代碼如下:

File file = new File("xxx.txt");
// 實例化輸入流
FileReader reader = new FileReader(file);
// 緩沖區
char[] buffer = new char[1024];

// 分次讀取數據,每次最多讀取1024個字符,將數據讀取到緩沖區之中,同時返回讀取的字節個數
int len;
while ((len = reader.read(buffer)) > -1) {
    // 字符轉為字符串
    String msg = new String(buffer, 0, len);
    System.out.println(msg);
}

// 刪除文件
file.delete();

經過排查,發現出現該問題的原因是:讀取文件的 IO 流沒有正常的關閉,導致文件一直被流持有,刪除文件不成功!

那這么解決這個問題呢?答案其實也很簡單,當讀完 IO 流的數據或者寫完數據,手動調用一下關閉流的方法,最后再進行刪除文件。

// 刪除文件之前,先將 IO 流關閉
reader.close();

// 刪除文件
file.delete();

可能有的同學會發出疑問,為什么 IO 流必須手動關閉,不能像其他的方法一樣坐等 GC 回收?

今天我們就一起來聊聊這個話題,以及如何正確的關閉 IO 流操作。

二、為什么 IO 流需要手動關閉?

熟悉編程語言的同學,可能知道,無論是 C 語言還是 C++,都需要手動釋放內存,但是 Java 不需要。

這主要得益于 Java 的虛擬機垃圾回收機制,它可以幫助開發者自動回收內存中的對象,不需要手動釋放內存,但是有些東西它是無法回收的,例如端口、顯存、文件等,超出了虛擬機能夠釋放資源的界限。

如果對未關閉流的文件進行讀寫操作,可能就會報錯,告訴你這個文件被某個進程占用。如果不手動釋放資源,隨著資源占有量逐漸增多,垃圾會越來越多,最終可能導致系統無法存儲其他的資源,甚至會出現系統崩潰。

一般來說,只要存在 IO 流讀寫操作,無論使用到的是網絡 IO 或者文件 IO,都是需要和計算機內的資源打交道的,清理計算機上面的垃圾,Java 的虛擬機垃圾回收機制沒有這個能力。

熟悉 Java 虛擬機垃圾回收機制的同學,可能知道 gc 有兩個顯著的特點:

  • gc 只能釋放內存資源,而不能釋放與內存無關的資源
  • gc 回收具有不確定性,也就是說你根本不知道它什么時候會回收

所以進行流的操作時,凡是跨出虛擬機邊界的資源都要求程序員自己手動關閉資源。

可能有的同學又發出疑問,我平時本地測試的時候沒有發現這個問題,為什么部署到線上就出這個提示的呢?

以讀取文件的FileInputStream流為例,其實里面隱含了一個finalize方法,當虛擬機進行垃圾回收之前,會調用這個方法。

打開源碼,你會發現底層調用的其實是close釋放資源的方法,可以看到 JDK 間接的幫助開發者進行最后一次的兜底。

/**
 * Ensures that the <code>close</code> method of this file input stream is
 * called when there are no more references to it.
 *
 * @exception  IOException  if an I/O error occurs.
 * @see        java.io.FileInputStream#close()
 */
protected void finalize() throws IOException {
    if ((fd != null) &&  (fd != FileDescriptor.in)) {
        /* 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();
    }
}

這就解釋了,為什么只是時不時的會出現提示,并不是總是。這個方法什么時候被調用,這取決于虛擬機的垃圾回收頻次。

但是在實際的開發過程中,開發者不能完全依賴虛擬機幫你回收這些系統資源,只要涉及到流的操作,強烈建議大家一定要手動關閉釋放資源,避免出現一些不必要的bug。

具體如何手動釋放資源資源呢,我們接著看!

三、正確的關閉流姿勢介紹

我們深知在操作 Java 流對象后要將流進行關閉,但是現實的情況卻往往不盡人意,原因是每個開發者的寫法可能不盡相同,不同的寫法導致出現各種千奇百怪的問題,下面我們一起來看看幾種關閉流的代碼案例!

寫法 1:在 try 中關流,而沒在 finally 中關流

try {
    OutputStream out = new FileOutputStream("file");
    // ...操作流代碼
    out.close();
} catch (Exception e) {
    e.printStackTrace();
}

當操作流代碼報錯的時候,這種寫法會導致流無法正常的關閉,因此不推薦采用!

正確的操作方式,應該在finally里面完成,實例代碼如下:

OutputStream out = null;
try {
    out = new FileOutputStream("file");
    // ...操作流代碼
} catch (Exception e) {
    e.printStackTrace();
} finally {
    // 在 finally 中進行關閉,確保一定能被執行
    try {
        if (out != null) {
            out.close();
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

寫法 2:在關閉多個流時,將其放在一個 try 中

在關閉多個流時,有的同學嫌棄麻煩,將其放在一個 try 中完成,實例代碼如下:

OutputStream out1 = null;
OutputStream out2 = null;
try {
    out1 = new FileOutputStream("file");
    out2 = new FileOutputStream("file");
    // ...操作流代碼
} catch (Exception e) {
    e.printStackTrace();
} finally {
    try {
        if (out1 != null) {
            // 如果此處出現異常,則out2流沒有被關閉
            out1.close();
        }
        if (out2 != null) {
            out2.close();
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

這種寫法下,當out1.close出異常的時候,out2.close是不會被正常關閉的,因此不推薦采用!

正確的操作方式,應該是一個一個的close,別偷懶,實例代碼如下:

OutputStream out1 = null;
OutputStream out2 = null;
try {
    out1 = new FileOutputStream("file");
    out2 = new FileOutputStream("file");
    // ...操作流代碼
} catch (Exception e) {
    e.printStackTrace();
} finally {
    try {
        if (out1 != null) {
            out1.close();
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
    try {
        if (out2 != null) {
            out2.close();
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

寫法 3:在循環中創建流,在循環外關閉

有的同學在循環操作多個文件時,在循環外關閉文件流,實例代碼如下:

OutputStream out = null;
try {
    for (int i = 0; i < 10; i++) {
        out = new FileOutputStream("file");
        // ...操作流代碼
    }
} catch (Exception e) {
    e.printStackTrace();
} finally {
    try {
        if (out != null) {
            out.close();
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

表面看上去好像沒有問題,但是實際上創建了 10 個 IO 流,try 里面的邏輯執行完成之后,只是把最后的一個 IO 流對象賦予給了out參數。也就是當程序執行完畢之后,只關閉了最后一個 IO 流,其它 9 個 IO 流沒用被手動關閉,因此不推薦采用!

正確的操作方式,應該是在循環體內close,別偷懶,實例代碼如下:

for (int i = 0; i < 10; i++) {
    OutputStream out = null;
    try {
        out = new FileOutputStream("file");
        // ...操作流代碼
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        try {
            if (out != null) {
                out.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

寫法 4:關閉多個流時,沒用遵循后定義先釋放原則

有的同學在操作多個文件流時,操作完成之后,依照先后次序進行關閉文件流,實例代碼如下:

FileOutputStream fos = null;
BufferedOutputStream bos = null;
try {
    fos = new FileOutputStream("file");
    bos = new BufferedOutputStream(fos);
    // ...操作流代碼
} catch (Exception e){

} finally {
    // 依次關閉流
    try {
        fos.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
    try {
        // 此處會報 java.io.IOException: Stream Closed 錯誤
        bos.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

按照先后順序關閉文件流,這種寫法下,有可能會報java.io.IOException: Stream Closed錯誤。

原因是BufferedOutputStream依賴于FileOutputStream,如果直接關閉FileOutputStream流,再次關閉BufferedOutputStream,會提示源頭已經被關閉,緩存區數據無法輸出。

正確的操作方式,應該遵循后定義先釋放的原則,實例代碼如下:

FileOutputStream fos = null;
BufferedOutputStream bos = null;
try {
    fos = new FileOutputStream("file");
    bos = new BufferedOutputStream(fos);
    // ...操作流代碼
} catch (Exception e){

} finally {
    // 后定義先釋放
    try {
        bos.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
    try {
        fos.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

寫法 5:jdk7 及以上版本,推薦采用 try-with-resources 寫法

try-with-resources是 JDK 7 中引入的一個新的異常處理機制,它能讓開發人員不用顯式的釋放try-catch語句塊中使用的資源。

以上文為例,可以改成如下寫法:

try (FileOutputStream fos = new FileOutputStream("file");
     BufferedOutputStream bos = new BufferedOutputStream(fos)){
    // ...操作流代碼
} catch (Exception e){
    e.printStackTrace();
}

try-with-resources釋放資源的操作,也是遵循的后定義先釋放的原則!

寫法 6:使用包裝流時,只需要關閉最后面的包裝流即可

包裝流是指通過裝飾設計模式實現的 IO 流類,其目的是對底層流的功能進行擴展,在實際數據傳輸的時候,還是使用底層流進行傳輸。比如緩存字節輸出流BufferedOutputStream就是一個包裝流,目的是對字節輸出流提供一個緩存區功能,讓數據輸出效率更高。

在使用到包裝流的時候,我們只需要關閉最后面的包裝流即可。

以上文為例,改寫的實例代碼如下:

InputStream is = null;
InputStreamReader isr = null;
BufferedReader br = null;
try {
    is = new FileInputStream("file");
    isr = new InputStreamReader(is);
    br = new BufferedReader(isr);
    // ...操作流代碼
} catch (Exception e){
    e.printStackTrace();
} finally {
    // 關閉包裝流,也會自動關閉 InputStream 流
    try {
        br.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

這是因為,包裝流關閉時會調用原生流的關閉方法,請看源碼!

public void close() throws IOException {
    synchronized (lock) {
        if (in == null)
            return;
        try {
            // 這里的in 指的是 InputStreamReader,最后會原生流的close方法
            in.close();
        } finally {
            in = null;
            cb = null;
        }
    }
}

四、內存流是否需要關閉?

在上文中,我們提到只要是 IO 流都建議大家手機關閉資源,但是在 Java 中有一種流,它是不需要手動關閉的,比如內存讀寫流:ByteArrayInputStream、ByteArrayOutputStream。

不同于指向硬盤的流,ByteArrayInputStream和ByteArrayOutputStream其實是偽裝成流的字節數組存儲在內存中(把它們當成字節數據來看就好了),他們不會鎖定任何文件句柄和端口,如果不再被使用,字節數組會被垃圾回收掉,所以不需要關閉。

當 IO 流是指向存儲卡 / 硬盤 / 網絡等外部資源的流,是一定要手動關閉的。

五、小結

本位主要圍繞【為什么 IO 流必須手動關閉,不能像其他的方法坐等 GC 處理】這個話題進行一次內容的整合和總結,同時也給出了推薦的正確關閉 IO 流的寫法。

在實際的開發過程中,建議大家正確的使用 IO 流,以免出現各種 bug !

內容難免有所遺漏,歡迎網友留言指出。

六、參考

1、https://blog.csdn.net/weixin_44182586/article/details/101392261

2、https://blog.csdn.net/u012643122/article/details/38540721

責任編輯:武曉燕 來源: 潘志的研發筆記
相關推薦

2023-07-07 07:40:10

C++JavaC 語言

2025-01-14 08:42:34

IO流程序語句

2023-12-07 12:21:04

GCJVM垃圾

2015-04-03 12:31:26

OracleSalesforce并購

2019-11-17 22:38:13

PAM特權訪問管理CISO

2023-11-26 00:24:33

2022-10-08 18:25:22

Python內存管理GC

2014-07-02 16:51:08

WOT2014高效技術團隊

2010-08-12 10:54:26

惠普

2020-10-30 14:19:52

密碼手機重啟指紋

2021-12-17 22:56:27

前端測試框架

2021-02-28 13:22:29

物聯網5G技術

2022-01-11 14:55:14

Python垃圾回收解釋器

2022-09-01 18:11:51

iOS安卓內存

2022-04-02 09:32:06

大數據數據智能企業

2023-12-19 07:56:08

微服務軟件測試左移測試

2021-04-25 09:23:43

XDRMDR網絡安全

2022-05-22 21:23:10

前端監控系統

2012-12-18 11:55:38

2021-07-30 06:49:40

SSD內存CPU
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 在线观看亚洲专区 | 国产影音先锋 | 国产精品特级毛片一区二区三区 | av影音 | 在线成人一区 | 精品国产1区2区3区 在线国产视频 | 精品一区二区三区入口 | 欧美亚洲一区二区三区 | 天堂av免费观看 | 99久久婷婷国产综合精品电影 | 91在线导航 | 亚av在线 | 日韩影院一区 | 欧美精品综合在线 | 午夜私人影院 | 精品久草| 国产精品精品 | 国产视频福利一区 | 激情a| 亚洲欧美日韩成人在线 | 中文字幕精品一区二区三区精品 | 日韩欧美国产一区二区三区 | 国产精品不卡一区 | 中文字幕的av| 日韩成人在线播放 | 欧美色综合一区二区三区 | 欧美日韩精选 | 91麻豆蜜桃一区二区三区 | 日韩一区二区在线视频 | 国产高清不卡 | 国产小视频自拍 | 国产成人a亚洲精品 | 在线观看免费毛片 | 亚洲成人在线视频播放 | 亚洲男人天堂 | 亚洲有码转帖 | 国产精品久久久乱弄 | 国产日韩免费观看 | 欧美精品video | 亚洲一一在线 | 日韩av网址在线观看 |