java中是否所有的stream流都需要主動關閉

流的概念

在輸出數據時,內存中的特定數據排成一個序列,依次輸出到文件中,這個數據序列就像流水一樣源源不斷地“流”到文件中,因此該數據序列稱爲輸出流。同樣,把文件中的數據輸入到內存中時,這個數據序列就像流水一樣“流”到內存中,因此把該數據序列稱爲輸入流。

 

輸入流與輸出流

爲什麼要按照流的方式來讀取和保存數據呢?

因爲流可以保證原始數據的先後順序不會被打亂,在很多情況下,這都是符合實際需求的。比如讀一篇文章,肯定是希望從頭到尾的讀取文章,而不希望打亂文章的章節順序。

無論是輸入流還是輸出流,如果數據序列中最小的數據單元是字節,那麼稱這種流爲字節流。

數據單元是字節的字節流如果數據序列中最小的數據單元是字符,那麼稱這種流爲字符流。

InputStream爲什麼設計成不能重複讀呢? 

“在InputStream讀取的時候,會有一個pos指針,他指示每次讀取之後下一次要讀取的起始位置,當讀到最後一個字符的時候,pos指針不會重置。” 
說的也有道理,就是說InputStream的讀取是單向的。但是並不是所有的InputStream實現類都是這樣的實現方式。

//BufferedInputStream代碼片段:  
 public synchronized int read() throws IOException {  
        if (pos >= count) {  
            fill();  
            if (pos >= count)  
                return -1;  
        }  
        return getBufIfOpen()[pos++] & 0xff;  
    }  
  
//FileInputStream代碼片段:  
public native int read() throws IOException;

我們知道: 
Java 的List內部是使用數組實現的,遍歷的時候也有一個pos指針。但是沒有說List遍歷一個第二次遍歷就沒有了。第二次遍歷是創建新的Iterator,所以pos也回到了數組起始位置。對於某些InputStream當然可以也這麼做。例如:ByteArrayInputStream 
ByteArrayInputStream就是將一個Java的byte數組保存到對象裏,然後讀取的時候遍歷該byte數組。 

public ByteArrayInputStream(byte buf[]) {  
        this.buf = buf;  
        this.pos = 0;  
        this.count = buf.length;  
}  
  
public synchronized int read() {  
        return (pos < count) ? (buf[pos++] & 0xff) : -1;  
}

就ByteArrayInputStream而言,要實現重複讀取是很簡單的,但是爲什麼沒有。我想是爲了遵循InputStream的統一標準。 
在InputStream的read方法的註釋上明確說明: 

/** 
     * Reads the next byte of data from the input stream. The value byte is 
     * returned as an <code>int</code> in the range <code>0</code> to 
     * <code>255</code>. If no byte is available because the end of the stream 
     * has been reached, the value <code>-1</code> is returned. This method 
     * blocks until input data is available, the end of the stream is detected, 
     * or an exception is thrown. 
     * 
     * <p> A subclass must provide an implementation of this method. 
     * 
     * @return     the next byte of data, or <code>-1</code> if the end of the 
     *             stream is reached. 
     * @exception  IOException  if an I/O error occurs. 
     */  
    public abstract int read() throws IOException;  

當流到達末尾後,返回-1.  

InputStream顧名思義就是一個單向的字節流,跟水流一樣,要想再次使用就自己再去源頭取一下。 

你看看上面那個圖就明白了,InputStream是中間的管道,並不是左右兩邊的桶要想喝水了,就在把水管架在水源與杯子之間,讓水流到杯子裏(注意:這個動作完成了之後水管裏面就沒有水了)。 

這樣看來,InputStream其實是一個數據通道,只負責數據的流通,並不負責數據的處理和存儲等其他工作範疇。 
前面講過,其實有的InputStream實現類是可以實現數據的處理工作的。但是沒有這麼做,這就是規範和標準的重要性。

流在傳輸過程中的緩衝區概念

先上個例子:

public class FlushTest {
public static void main(String[] args) throws IOException {
FileReader fileReader = new FileReader("F:\\Hello1.txt"); //大文件
FileWriter fileWriter = new FileWriter("F:\\Hello2.txt");
int readerCount = 0;
//一次讀取1024個字符
char[] chars = new char[1024];
while (-1 != (readerCount = fileReader.read(chars))) {
fileWriter.write(chars, 0, readerCount);
}
}
}

這裏並沒有調用close()方法。close()方法包含flush()方法 ,即close會自動flush結果:

 可以看到,複製的文件變小了。明顯,數據有丟失,丟失的就是緩衝區“殘餘”的數據。在計算機層面,Java對磁盤進行操作,IO是有緩存的,並不是真正意義上的一邊讀一邊寫,底層的落盤(數據真正寫到磁盤)另有方法。所以,最後會有一部分數據在內存中,如果不調用flush()方法,數據會隨着查詢結束而消失,這就是爲什麼數據丟失使得文件變小了。

BufferedOutputStream、BufferedFileWriter 同理再舉個例子:

class FlushTest2{
public static void main(String[] args) throws IOException {
FileWriter fileWriter = new FileWriter("F:\\Hello3.txt");
fileWriter.write("今天打工你不狠,明天地位就不穩\n" +
"今天打工不勤快,明天社會就淘汰");
}
}

不調用flush()方法你會發現,文件是空白的,沒有把數據寫進來,也是因爲數據在內存中而不是落盤到磁盤了。所以爲了實時性和安全性,IO在寫操作的時候,需要調用flush()或者close()

close() 和flush()的區別:

關close()是閉流對象,但是會先刷新一次緩衝區,關閉之後,流對象不可以繼續再使用了,否則報空指針異常。

flush()僅僅是刷新緩衝區,準確的說是"強制寫出緩衝區的數據",流對象還可以繼續使用。

總結一下:

1、Java的IO有一個 緩衝區 的概念,不是Buffer概念的緩衝區。

2、如果是文件讀寫完的同時緩衝區剛好裝滿 , 那麼緩衝區會把裏面的數據朝目標文件自動進行讀或寫(這就是爲什麼總剩下有一點沒寫完) , 這種時候你不調用close()方法也0不會出現問題

3、如果文件在讀寫完成時 , 緩衝區沒有裝滿,也沒有flush(), 這個時候裝在緩衝區的數據就不會自動的朝目標文件進行讀或寫 , 從而造成緩衝區中的這部分數據丟失 , 所以這個是時候就需要在close()之前先調用flush()方法 , 手動使緩衝區數據讀寫到目標文件。

舉個很形象的例子加深理解:我從黃桶通過水泵把水抽到綠桶,水管就相當於緩衝區,當我看到黃桶水沒有了,我立馬關了水泵,但發現水管裏還有水沒有流到綠桶,這些殘留的水就相當於內存中丟失的數據。如果此時我再把水泵打開,把水管裏的水都抽了一遍,此時水管裏面的水又流到了綠桶,這就相當於調用了flush()方法。

java Stream對象如果不關閉會發生什麼?

比如FileStream或者說HttpClient 中的HTTPResponse,不關閉會發生什麼呢?或者說調用close防範實際上在底層都做了哪些事?

看是什麼類了, 不同的類的close裏執行的邏輯當然是不一樣的. close就是用來做收尾工作的, 如果你學過servlet, 可以認爲就是servletdestroy方法.
有一些類會佔用特殊資源(比如文件句柄, 線程, 數據庫連接等), 而這些資源是有限的/比較消耗性能的, 而且不會自動釋放(或者需要很久才能自動釋放), 因此需要你在不用的時候及時釋放, 避免浪費資源.

比如IO裏面的:

  • FileInputStream會佔用系統裏的一個文件句柄, 每個進程可以打開的文件數量是有限的, 如果一直打開而不關閉, 理論上遲早會遇到無法打開的情況.
  • StringWriter就沒有什麼. close方法沒什麼卵用
Closing a StringWriter has no effect. The methods in this class can be called after the stream has been closed without generating an IOException.

ps: FileInputStreamfinalize方法會自動調用close方法. 但是需要等待很長很長時間. 所以最好自己手工調用.

一般而言, 如果是接口裏有close方法, 我們調用時是不應該關注close裏究竟執行了什麼, 不調用是不是有壞處, 而應該是始終調用

你打開文件是會在系統裏有一個文件句柄的,這個句柄數量操作系統裏是有限的,如果不close,這個句柄所代表的資源就泄露了,就跟懸垂指針一樣,如果量大或時間長了之後再打開文件就可能打不開了,超過了系統的限制

有沒有不需要關閉的流

曾幾何時,作爲java程序員要記住的一條準則就是,流用完了一定要在關閉,一定要寫在finally裏。

finally {
	out.flush();
 	out.close();
}

但是最近發現一個stream是不需要關閉的。它就是ByteArrayOutputStream,當然還有它的妹妹ByteArrayInputStream和表哥StringWriter。道理一樣,我們就不討論親戚們了。 作爲一種OutputStream它也extendsOutputStream,自然也有繼承了flush()close()。 但這2個方法的方法體爲空。

    /**
     * Closing a <tt>ByteArrayOutputStream</tt> has no effect. The methods in
     * this class can be called after the stream has been closed without
     * generating an <tt>IOException</tt>.
     */
    public void close() throws IOException {
    }
    
    /***
    * OutputStream的方法,ByteArrayInputStream並未重寫
    */
    public void flush() throws IOException {
    }    

究其原因也不難理解。其實ByteArrayInputStream內部實現是一個byte數組,是基於內存中字節數據的訪問。並沒有佔用硬盤,網絡等資源。就算是不關閉,用完了垃圾回收器也會回收掉。這點跟普通數組並沒有區別。既然是操作內存,就要考慮到內存大小,如果字節流太大,就要考慮內存溢出的情況。

但是,作爲一個蛋疼的程序員,習慣性關閉流是一個好習慣,不管三七五十八,先close掉再說,現在close是空方法,保不齊哪天就有了呢?這也是百利無一害的事,就好像保健品,吃了不治病,但是也喫不壞。

  • 結論就是:指向內存的流可以不用關閉,指向硬盤/網絡等外部資源的流一定要關閉。

 

本篇文章如有幫助到您,請給「翎野君」點個贊,感謝您的支持。

首發鏈接:https://www.cnblogs.com/lingyejun/p/18156434

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章