流的概念
在輸出數據時,內存中的特定數據排成一個序列,依次輸出到文件中,這個數據序列就像流水一樣源源不斷地“流”到文件中,因此該數據序列稱爲輸出流。同樣,把文件中的數據輸入到內存中時,這個數據序列就像流水一樣“流”到內存中,因此把該數據序列稱爲輸入流。
輸入流與輸出流
爲什麼要按照流的方式來讀取和保存數據呢?
因爲流可以保證原始數據的先後順序不會被打亂,在很多情況下,這都是符合實際需求的。比如讀一篇文章,肯定是希望從頭到尾的讀取文章,而不希望打亂文章的章節順序。
無論是輸入流還是輸出流,如果數據序列中最小的數據單元是字節,那麼稱這種流爲字節流。
數據單元是字節的字節流如果數據序列中最小的數據單元是字符,那麼稱這種流爲字符流。
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數組。
就ByteArrayInputStream而言,要實現重複讀取是很簡單的,但是爲什麼沒有。我想是爲了遵循InputStream的統一標準。
在InputStream的read方法的註釋上明確說明:
當流到達末尾後,返回-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
, 可以認爲就是servlet
的destroy
方法.
有一些類會佔用特殊資源(比如文件句柄, 線程, 數據庫連接等), 而這些資源是有限的/比較消耗性能的, 而且不會自動釋放(或者需要很久才能自動釋放), 因此需要你在不用的時候及時釋放, 避免浪費資源.
比如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: FileInputStream
的finalize
方法會自動調用close
方法. 但是需要等待很長很長時間. 所以最好自己手工調用.
一般而言, 如果是接口裏有close
方法, 我們調用時是不應該關注close
裏究竟執行了什麼, 不調用是不是有壞處, 而應該是始終調用
你打開文件是會在系統裏有一個文件句柄的,這個句柄數量操作系統裏是有限的,如果不close,這個句柄所代表的資源就泄露了,就跟懸垂指針一樣,如果量大或時間長了之後再打開文件就可能打不開了,超過了系統的限制
有沒有不需要關閉的流
曾幾何時,作爲java程序員要記住的一條準則就是,流用完了一定要在關閉,一定要寫在finally
裏。
finally { out.flush(); out.close(); }
但是最近發現一個stream是不需要關閉的。它就是ByteArrayOutputStream
,當然還有它的妹妹ByteArrayInputStream
和表哥StringWriter
。道理一樣,我們就不討論親戚們了。 作爲一種OutputStream它也extends
了OutputStream
,自然也有繼承了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是空方法,保不齊哪天就有了呢?這也是百利無一害的事,就好像保健品,吃了不治病,但是也喫不壞。
- 結論就是:指向內存的流可以不用關閉,指向硬盤/網絡等外部資源的流一定要關閉。
本篇文章如有幫助到您,請給「翎野君」點個贊,感謝您的支持。