JAVA IO的那些事

話題一:編碼/轉碼網絡

咱們知道,在網絡傳輸過程當中,說到底,是要傳輸字節流的,字符流(Writer/Reader)不過是在字節流(InputStream/OutputStream)基礎上作了一下封裝而已,是JAVA在語法層面上給咱們作的一個東西。ide


下面咱們來先看看一段代碼:編碼

wKioL1YrPTqCuY0dAAHxAGvG_sU621.jpg


運行結果:spa

get info : 涓栫晫錛屼綘濂?設計

get info : 世界,你??代理



分析:對象


首先來講,S1是亂碼,這個是好理解的,但是爲何S2也是亂碼呢?blog


第一,S經過UTF-8編碼造成字節數據B接口


第二,接收方拿到B,經過GBK來進行轉碼,獲得S1,很顯然會亂碼。ip


更加劇要的一點是:極可能B在GBK字符集中根本沒有任何映射,那麼此時GBK會指定一些特殊符號代替,好比?


第三,利用S1想還原原先的字節流的想法可能失敗!


由於在上面的第二中,原來有些字節數據經過GBK的轉碼,可能被弄成?,而?在GBK中的字節數據和原先的字節數據就極可能不同了!因而S2就這樣被亂碼了。



這說明,在編碼的轉換過程當中,是極可能轉不回來的!



話題二:網絡交換數據的IO過程

wKioL1Z-erijYrS_AAA2tUp6-xI359.png

從上面的圖能夠看出,JAVA自己不參與數據在網絡上的傳輸,JAVA僅僅作的是,若是要發送數據,那麼把字節流交給KERNEL;若是須要讀取數據,那麼從KERNEL中讀取字節流。


發送與接收,若是採起一致的編碼,那麼就不會亂碼!


在網絡中傳輸的,應該是一種協議,不過是用字節流表達而已,好比,字節流的編碼,大小,字節內容等。


話題三:JAVA BIO

Java BIO,即傳統的IO,阻塞的IO,也是最經常使用,最基本的IO了,下面就來剖析下它!


  • Input/Output

Input:輸入,把數據從磁盤、鍵盤、網絡,內存中輸入到程序中;

Output:輸出,把程序中的數據輸出到磁盤、顯示器、網絡、內存中;

能夠說,沒有IO,就沒有結果,就沒有價值!


  • 字節流 VS 字符流

字節流的源頭是:InputStream/OutputStream

字符流的源頭是:Reader/Writer


首先來講,咱們必定得忘記字符流這回事,而是從字節流的角度進行理解IO。


之因此,存在字符流,是由於畢竟字節不是那麼可視化,所以提供了把字節流轉化成字符流的方式。


咱們能夠關注下Reader/Writer的子類:

wKiom1Z_RsmiB2eiAAATKDisQrQ181.png


wKioL1Z_R0Cw-8_GAAASUbJR9Z0058.png


能夠清楚的看到,在InputStreamReader/OutputStreamWriter提供的構造方法中,接受一個字節流以及編碼。其實,這就是字節流轉化成字符流的橋樑。咱們須要注意的是,在這個轉化過程當中,須要特別注意編碼問題,最好就是手動的、明確的指定編碼,而不是走默認的環境編碼什麼的。


  • DataInputStream/DataOutputStream

這種數據流有個特色,就是提供了各類readXXX/writeXXX方法,以下所示:

wKiom1Z_SsDgL4XJAAA4eM9PR64233.png

試想下,若是咱們想經過網絡,給對方發送個int,咱們難道得本身將int轉成4個字節來進行發送?這也太麻煩了吧,而DataInputStream已經提供好了衆多數據格式的操做了。在Socket通訊時,當咱們須要封裝Socket的IO操做時,能夠考慮用這個數據流。


  • ByteArrayInputStream/ByteArrayOutputStream

內存字節流,這種流,會將內存中的字節包裝造成流,延伸來看,String也能夠先獲取byte[],而後在轉化成這種內存字節流。

wKiom1Z_TZ2ipBPcAAAG7R8NSHg480.png


  • ObjectInputStream/ObjectOutputStream

對象字節流,類比的思想,就是將對象包裝造成字節流,而後,能夠寫入磁盤(持久化),能夠經過網絡發送出去(對方接受後,將字節流反序列化獲得對象)。


  • 壓縮流

ZipInputStream/GZinputStream/JarInputStream...

這些壓縮流,能夠套在文件字節流上,也能夠套在網絡字節流上(節省網絡通訊量)。


  • BufferedInputStream/BufferedOutputStream

若是咱們想要Buffer功能,能夠套一層Buffer流,Buffer每每會快點,這是爲何呢?


下面咱們簡單來看看BufferedOutputStream的實現:

wKioL1Z_UaTBpxWVAAAEfYc6N48524.png

wKioL1Z_UcOh8gWUAAAKdQDFdAc565.png

wKioL1Z_USKRssWaAAAWEDZ_MJQ518.png

wKioL1Z_UUyypRrQAAAR3By3SyA028.png

能夠看到在BufferedOutputStream內部維護了一個byte[],默認構造的狀況下,大小是8192。

若是寫,沒有超出byte[]大小,那麼寫入byte[],這個過程固然至關快;若是超出了byte[]大小,那麼flushBuffer,而flushBuffer纔是真正寫入的地方,並且是一次性的寫入byte[]。

實際上,利用Buffer功能,將隨機寫變成了順序寫,固然快了!


  • PrintStream

打印字節流,咱們常用到的System.out,System.err就是PrintStream對象,提供了衆多數據格式的print方法。在重定向中,利用System.setOut/setErr,其實都是set到一個PrintStream上。


話題四:IO中的裝飾思想

IO流涉及的類那麼多,每種流都有本身的特色,若是但願多種功能/特色都彙集在一塊兒,那麼就要進行包裝和裝飾功能了。咱們能夠以最簡單的BUFFER功能,來分析下這個裝飾思想。

wKioL1Z_f6yBMgFSAAAbLj3oWMg281.png


經過閱讀FilterOutputStream類的源碼,發現FilterOutputStream類持有OutputStream類的引用,它的每一個方法,都沒有幹啥,就是對持有的OutputStream的引用進行調用對應方法罷了。


BufferedOutputStream extends FilterOutputStream,它內部經過本身的byte[],實現BUFFER功能。


到這裏,咱們好像發現點端倪了,開始有了些猜想:

那些有特色的IO類,是否是經過extends FilterXXXStream,而後在本身內部「耍些手段」來實現特定功能的呢?


wKioL1Z_go3iS-kwAABlEmJUvlg281.png


經過上圖,能夠發現,的確有些有特色的IO類extends FilterXXXStream,好比DataXXXStream/BufferedXXXStream/PrintStream等。


FilterXXXStream就好像一層代理,雖然它什麼都沒有作,僅僅是經過引用去調用對應方法而已;這樣看似無用,其實否則,好比想實現buffer功能,只須要extends FilterXXXStream,而後僅僅重寫read/write方法便可,這樣不少代碼不須要再寫,由於從FilterXXXStream上已經得到。Java這樣的設計,巧妙!


話題五:flush() / close() 

流用完了,要關閉流時,咱們常常想到要flush,要close,下面咱們來分析分析它們!


wKiom1Z_h2zxiv0cAAAJbD4eLlk375.png

2個標示接口,Closeable須要close,Flushable須要flush。


在OutputStream中,實際上它們是空的方法:

wKioL1Z_iLPwCRfyAAAIEyZFZSw969.png

wKiom1Z_iKbxJcA3AAAG_JwvX44604.png


很顯然,這是但願子類本身去flush,close。若是用到了Buffer機制,特別是經過修飾的功能擁有了BufferedXXXStream的特色,那麼必定得注意flush/close,其實是將byte[]中未處理的數據處理掉。


注意到FileXXXStream並無去重寫flush方法,若是extends FileXXXStream類,又沒有提供flush的實現,那麼將調用的是OutputStream中一個空的flush方法!好比SocketOutputStream extends FileOutputStream,卻沒有提供重寫的flush方法,說明在SOCKET通訊中,並不須要咱們去調用flush方法,它是在內核層面上作的緩衝,而不是JAVA層面的。


所以,對一個IO類,要不要flush,要不要close,咱們得看看它extends who?有buffer機制嗎?


話題六:Java BIO vs NIO vs AIO

關於Java BIO/NIO,在前面的博客中已經有所介紹,你們能夠參考下面的:

http://zhangfengzhe.blog.51cto.com/8855103/1715530

http://zhangfengzhe.blog.51cto.com/8855103/1726488

http://zhangfengzhe.blog.51cto.com/8855103/1712845


Java BIO:小張同窗有一份快遞,因而他去物流中轉站去拿貨,他在那裏等着,一直等到快遞來,這段期間,他哪裏也不去。


Java NIO:小張同窗,天天去一趟物流中轉站,檢測下是否是快遞的貨到了,若是到了,就帶回來,若是沒有則返回,明天繼續。


Java AIO:小張同窗並不去物流中轉站,若是貨到了,那麼快遞員「送貨上門」。


上面的例子,形象的說明了BIO/NIO/AIO的特色和區別,胖哥說的好,存在就是有價值的,AIO是最新出來的東西,想法是好的,可是不必定是適合的。好比JDBC程序中,咱們發了一個SQL,可是這是個AIO,那麼麻煩了,發出這個SQL後,程序馬上向下執行,一段時間後SQL結果送來了,這個時候,咱們要拿出保存有發出SQL那個時刻的一些信息。想想,若是這樣的AIO不少的話,程序會複雜不少,保存不少信息,回調不少方法。


這也再次說明了一個道理,若是它簡單,那麼它內在確定很複雜,真是由於它內在的複雜,致使了外在的簡單!


到這裏,JAVA IO那些事就結束了,你學到了嗎?

相關文章
相關標籤/搜索