原創文章, 轉載請私信. 訂閱號 tastejava 回覆 bio思維導圖獲取思維導圖源文件
BIO NIO AIO的概念
首先要明確阻塞/非阻塞即blocking/non-blocking和同步/異步即synchronous/asynchronous的概念, 阻塞和非阻塞指的是是否會阻塞當前線程, 例如調用ServerSocket的accept方法後, 當前線程就會阻塞等待連接建立. 同步和異步指的是一個操作結果需要主動獲取還是通過回調函數處理, 例如:
// 解釋異步同步概念僞代碼
// 主動獲取someMethod返回結果, 這種方式是同步的
Result result = someMethod();
// 通過回調參數中的處理方法執行結果, 這種方式是異步的
// 其中回調參數對象callback中包含someMethodWidthCallback返回結果的處理方法
// someMethodWidthCallback方法執行產生結果後, 內部會調用回調參數對象中的回調方法
Callback callback = new Callback();
someMethodWidthCallback(callback);
理解了上面的概念後, Java三種IO的概念或者各自的特點就可以理解了.
- BIO即BlockingIO 阻塞同步IO, JDK1.0, 1.1時代IO體系
- NIO即New-IO 非阻塞同步IO, JDK1.4時代的IO體系, 提供Non-Blocking的能力
- AIO即AsynchronousIO 非阻塞異步IO, JDK1.7時代對NIO的升級
BIO部分
BIO體系主要包括字符流和字節流兩類, 其中字節流可以處理一切數據, 包含字符類型數據. 字符流只能處理字符類型數據. 字符流是通過兩個字節-字符轉換流建立在字節流之上的工具流.
首先來分析一下java.io包下的字節流的結構(包含Deprecate內容):
如圖所示, 字節流又分爲輸入流和輸出流, 輸入和輸出是相對內存而言的概念, 往內存中讀取叫做輸入, 從內存中往其他目的地寫出數據叫做輸出, InputStream 是所有字節輸入流的父類, OutputStream 是所有字節輸出流的父類.
InputStream 描述了輸入流應該具有的方法, 是一個抽象類. 輸入流主要實現類以及作用爲
- ByteArrayInputStream 以byte數組作爲數據源構造輸入流實例, 提供了輸入流方式操作byte數組的能力.
- FileInputStream 以文件爲數據源, 可以通過文件路徑和文件對象方式構造輸入流實例, 提供從文件中讀取字節數組到內存的能力.
- ObjectInputStream 接收一個 InputStream 對象, 從中讀取對象實例到內存, 配合 ObjectOutputStream 使用
- PipedInputStream 提供多線程間通信的能力, 配合 PipedOutputStream 使用, 假設有 in 和 out 兩個Piped流實例, 通訊前首先要 in.connect(out) 或者 out.connect(in)對兩個實例進行關聯.
- SequenceInputStream 提供多個輸入流合併成一個輸入流並且順序讀寫的能力, 例如用輸入流實例 InputStream1 和 InputStream2來構造此流實例, 那麼 read 方法將會先讀取 InputStream1的數據, 然後再讀取 InputStream2 的數據
- StringBufferInputStream 此流已經由於無法正確的將字符轉換成字節而過時了, 不贅述.
細心的讀者會發現還沒有介紹FilterInputStream, FilterInputStream不提供任何功能, 不能直接被實例化, 接收一個輸入流實例, 它的幾種子類利用裝飾器模式爲原始的輸入流實例提供不同的能力.
- 子類BufferedInputStream, 對原始字節輸入流進行增強, 加入緩存, 避免頻繁讀取內存外的物理設備.
- 子類DataInputStream, 對原始字節流進行增強, 提供平臺無關的Java原始數據類型數據讀取能力
- 子類LineNumberInputStream, 對原始字節流進行增強, 提供行號管理, 讀取內容遇到換行符則行號自增一, 可以通過setLineNumber提供一個初始行號, 通過getLineNumber獲取當前行號
- 子類PushbackInputStream, 對原始字節流進行增強, 在 read 數據的基礎上提供 unread 方法, 將讀取的數據重新放回流中. 比如解析html文件, 先讀取數據獲得 html 編碼, 然後把所有數據 unread 回去, 再用指定編碼重新解析文件.
從字節流體系圖中可以看到, 輸入流和輸出流有幾個類命名是非常對稱的, 不再重複解釋, 提供的能力無非從輸入變成了輸出.唯一一個比較特別的是PrintStream, 這個流提供了很多打印方法, 如print, println 和 format, 看到這聰明的你一定發現了 System.out, System.err就是PrintStream的實例.
接下來分析一下java.io包下的字符流體系結構:
在字節流分析部分提到了, 字符流是通過兩個字節-字符轉換流建立在字節流上的工具流, 字符流提供了方便操作文本文件的能力. (字節-字符自動轉換).
字符流也是分爲輸入和輸出, 先看輸入Reader
- InputStreamReader 傳遞一個InputStream構造InputStreamReader實例, InputStreamReader可以指定字符集, 從輸入流中讀取數據得到的直接就是字符, 不需要讀取字節再轉換成字符.
- FileReader 是InputStreamReader的子類, 讀取字符文件的工具類, FileReader接收文件對象或文件路徑, 內部構造FileInputStream實例.FileReader提供默認的字符集合緩衝徐, 不需要自定義.
- CharArrayReader 使用字符數組當做數據源, 構造Reader實例
- StringReader 使用字符串當做數據源, 構造Reader實例
- PipedReader 與PipedWriter配合使用, 用於線程間傳遞字符,與PipedInputStream, PipedOutputStream類似, 在傳遞字符前要先關聯Reader和Writer, 例如inputReader.connect(outputReader)等價於outputReader.connect(inputReader)
接下來介紹一下應用裝飾者模式增強的三種工具Reader, 字符流的繼承體系比較奇怪, 三種工具Reader只有PushbackReader是繼承自FilterReader的, - FilterReader 本身不提供任何額外功能, 不能直接實例化, 爲子類裝飾做準備.
- PushbackReader 接收一個Reader構造實例, 與PushbackInputStream類似, 提供unread字符的能力
- BufferedReader 接收Reader構造實例, 與BufferedInputStream類似, 裝飾原始Reader提供緩存實現, 提高性能
- LineNumberReader 與LineNumberInputStream類似, 在原始Reader基礎上提供行號管理能力.
Writer與Reader大部分類的命名和功能也是對稱的, 不重複解釋, 唯一需要注意的是PrintWriter(since 1.1)跟PrintStream(since 1.0)功能重合, 但是PrintWriter可以接收Writer實例, 處理字符更推薦使用PrintWriter.
篇幅有限, NIO分析部分放在下一篇文章