話題一:編碼/轉碼網絡
咱們知道,在網絡傳輸過程當中,說到底,是要傳輸字節流的,字符流(Writer/Reader)不過是在字節流(InputStream/OutputStream)基礎上作了一下封裝而已,是JAVA在語法層面上給咱們作的一個東西。ide
下面咱們來先看看一段代碼:編碼
運行結果:spa get info : 涓栫晫錛屼綘濂?設計 get info : 世界,你??代理 分析:對象 首先來講,S1是亂碼,這個是好理解的,但是爲何S2也是亂碼呢?blog 第一,S經過UTF-8編碼造成字節數據B接口 第二,接收方拿到B,經過GBK來進行轉碼,獲得S1,很顯然會亂碼。ip 更加劇要的一點是:極可能B在GBK字符集中根本沒有任何映射,那麼此時GBK會指定一些特殊符號代替,好比? 第三,利用S1想還原原先的字節流的想法可能失敗! 由於在上面的第二中,原來有些字節數據經過GBK的轉碼,可能被弄成?,而?在GBK中的字節數據和原先的字節數據就極可能不同了!因而S2就這樣被亂碼了。 這說明,在編碼的轉換過程當中,是極可能轉不回來的! |
話題二:網絡交換數據的IO過程
從上面的圖能夠看出,JAVA自己不參與數據在網絡上的傳輸,JAVA僅僅作的是,若是要發送數據,那麼把字節流交給KERNEL;若是須要讀取數據,那麼從KERNEL中讀取字節流。
發送與接收,若是採起一致的編碼,那麼就不會亂碼!
在網絡中傳輸的,應該是一種協議,不過是用字節流表達而已,好比,字節流的編碼,大小,字節內容等。
話題三:JAVA BIO
Java BIO,即傳統的IO,阻塞的IO,也是最經常使用,最基本的IO了,下面就來剖析下它!
Input/Output
Input:輸入,把數據從磁盤、鍵盤、網絡,內存中輸入到程序中;
Output:輸出,把程序中的數據輸出到磁盤、顯示器、網絡、內存中;
能夠說,沒有IO,就沒有結果,就沒有價值!
字節流 VS 字符流
字節流的源頭是:InputStream/OutputStream
字符流的源頭是:Reader/Writer
首先來講,咱們必定得忘記字符流這回事,而是從字節流的角度進行理解IO。
之因此,存在字符流,是由於畢竟字節不是那麼可視化,所以提供了把字節流轉化成字符流的方式。
咱們能夠關注下Reader/Writer的子類:
能夠清楚的看到,在InputStreamReader/OutputStreamWriter提供的構造方法中,接受一個字節流以及編碼。其實,這就是字節流轉化成字符流的橋樑。咱們須要注意的是,在這個轉化過程當中,須要特別注意編碼問題,最好就是手動的、明確的指定編碼,而不是走默認的環境編碼什麼的。
DataInputStream/DataOutputStream
這種數據流有個特色,就是提供了各類readXXX/writeXXX方法,以下所示:
試想下,若是咱們想經過網絡,給對方發送個int,咱們難道得本身將int轉成4個字節來進行發送?這也太麻煩了吧,而DataInputStream已經提供好了衆多數據格式的操做了。在Socket通訊時,當咱們須要封裝Socket的IO操做時,能夠考慮用這個數據流。
ByteArrayInputStream/ByteArrayOutputStream
內存字節流,這種流,會將內存中的字節包裝造成流,延伸來看,String也能夠先獲取byte[],而後在轉化成這種內存字節流。
ObjectInputStream/ObjectOutputStream
對象字節流,類比的思想,就是將對象包裝造成字節流,而後,能夠寫入磁盤(持久化),能夠經過網絡發送出去(對方接受後,將字節流反序列化獲得對象)。
壓縮流
ZipInputStream/GZinputStream/JarInputStream...
這些壓縮流,能夠套在文件字節流上,也能夠套在網絡字節流上(節省網絡通訊量)。
BufferedInputStream/BufferedOutputStream
若是咱們想要Buffer功能,能夠套一層Buffer流,Buffer每每會快點,這是爲何呢?
下面咱們簡單來看看BufferedOutputStream的實現:
能夠看到在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功能,來分析下這個裝飾思想。
經過閱讀FilterOutputStream類的源碼,發現FilterOutputStream類持有OutputStream類的引用,它的每一個方法,都沒有幹啥,就是對持有的OutputStream的引用進行調用對應方法罷了。
BufferedOutputStream extends FilterOutputStream,它內部經過本身的byte[],實現BUFFER功能。
到這裏,咱們好像發現點端倪了,開始有了些猜想:
那些有特色的IO類,是否是經過extends FilterXXXStream,而後在本身內部「耍些手段」來實現特定功能的呢?
經過上圖,能夠發現,的確有些有特色的IO類extends FilterXXXStream,好比DataXXXStream/BufferedXXXStream/PrintStream等。
FilterXXXStream就好像一層代理,雖然它什麼都沒有作,僅僅是經過引用去調用對應方法而已;這樣看似無用,其實否則,好比想實現buffer功能,只須要extends FilterXXXStream,而後僅僅重寫read/write方法便可,這樣不少代碼不須要再寫,由於從FilterXXXStream上已經得到。Java這樣的設計,巧妙!
話題五:flush() / close()
流用完了,要關閉流時,咱們常常想到要flush,要close,下面咱們來分析分析它們!
2個標示接口,Closeable須要close,Flushable須要flush。
在OutputStream中,實際上它們是空的方法:
很顯然,這是但願子類本身去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那些事就結束了,你學到了嗎?