IO多路復用
先說明一個問題:在Java API中提供了兩套NIO,一套是針對標準輸入輸出NIO,另一套就是網絡編程NIO。網絡編程其實就是多了一個連接的過程,常用在Netty一些框架。本文主要講述標準輸入輸出NIO
一.BIO,NIO,AIO的區別
先說一些IO的模式
- 我們發現,IO不是我們想像中那麼簡單。要想進行IO操作,需要發一個請求給CPU,CPU得到通知後,此時CPU就需要調用操作系統內核程序(磁盤控制器)。這就是用戶態->內核態。
- 磁盤控制器接到通知,使用DMA技術將數據放到PageCache中,再由CPU把數據從內核程序傳回用戶程序(buffer)。這就是內核態->用戶態。
- 關於上圖的操作系統知識,如果不是清楚,請先移步//www.cnblogs.com/monkey-xuan/category/2067966.html,分3章講清楚操作系統核心內容。
根據上面的模型,其實我們可以把一次IO粗略的分為2個步驟:「內核數據準備好」和「數據從內核態拷貝到用戶態」
- 同步IO:
- 阻塞I/O:兩個步驟都要等待。(BIO)
- 適用於連接數目比較小,且固定的架構
- 阻塞I/O:兩個步驟都要等待。(BIO)
-
- 非阻塞I/O:第一個步驟無序等待,但是會一直詢問內核是否準備好。(BIO的多線程版)
-
- 多路復用I/O:在非阻塞的前提下優化,不會一直詢問。但是多一個select期。(NIO)
- 適用於連接數目多且連接比較短的操作(輕操作)。比如rpc框架,聊天服務器等。
- 多路復用I/O:在非阻塞的前提下優化,不會一直詢問。但是多一個select期。(NIO)
- 異步I/O: 兩個步驟都無需等待,詢問完交給內核自動完成。(AIO)
- 適用於連接數目多且連接比較長的操作(重操作),比如相框服務器,充分調用OS參與並發操作。
多路復用的系統方法說明:一直在升級
- select:用一個線程,接收到IO的請求,就將這些進行IO的文件描述符複製一份到內核,然後內存進行遍歷,看哪個文件已經準備好數據,將準備好的文件描述符的個數返回給用戶態,用戶態再進行遍歷,找到是哪個文件描述符的IO。有3個缺點如下
- select 調用需要傳入 fd 數組,需要拷貝一份到內核,高並發場景下這樣的拷貝消耗的資源是驚人的。(可優化為不複製)
- select 在內核層仍然是通過遍歷的方式檢查文件描述符的就緒狀態,是個同步過程,只不過無系統調用切換上下文的開銷。(內核層可優化為異步事件通知)
- select 僅僅返回可讀文件描述符的個數,具體哪個可讀還是要用戶自己遍歷。(可優化為只返回給用戶就緒的文件描述符,無需用戶做無效的遍歷)
- poll:它和 select 的主要區別就是,去掉了 select 只能監聽 1024 個文件描述符的限制。
- epoll:epoll 是最終的大 boss,它解決了 select 和 poll 的一些問題。
- 內核中保存一份文件描述符集合,無需用戶每次都重新傳入,只需告訴內核修改的部分即可。
- 內核不再通過輪詢的方式找到就緒的文件描述符,而是通過異步 IO 事件喚醒。
- 內核僅會將有 IO 事件的文件描述符返回給用戶,用戶也無需遍歷整個文件描述符集合。
這裡附上一個大佬寫的關於IO多路復用的動畫:IO多路復用
二.NIO的組成
Channel:
- 數據傳輸的雙向通道,既可以用來讀數據又可以用來寫數據。主要實現類如下:
- FileChannel:文件IO
- DatagramChannel:UDP的IO
- SocketChannel 和 ServerSocketChannel:TCP的IO
Buffer:
- 用於和 NIO 通道進行交互。
- Buffer 是一個頂層父類,它是一個抽象類,常用的 Buffer 的子類有:ByteBuffer、 IntBuffer、 CharBuffer、 LongBuffer、 DoubleBuffer、 FloatBuffer、ShortBuffer等
- 下圖為網絡IO的一個簡單圖
Selector:
- 選擇器 ,也可以翻譯為 多路復用器 。它是 Java NIO 核心組件中的一個,用於檢查一個或多個 NIO Channel(通道)的狀態是否處於可讀、可寫。如此可以實現單線程管理多個 channels,也就是可以管理多個網絡鏈接,就可以避免線程上下文切換。一個Selector對應一個線程,
三.Buffer中對應的Position, Mark, Capacity,Limit都啥?
- capacity:緩衝區容量的大小,就是裏面包含的數據大小。
- limit:對buffer緩衝區使用的一個限制,從這個index開始就不能讀取數據了。
- position:代表着數組中可以開始讀寫的index, 不能大於limit。
- mark:是類似路標的東西,在某個position的時候,設置一下mark,此時就可以設置一個標記
-
後續調用reset()方法可以把position複位到當時設置的那個mark上。去把position或limit調整為小於mark的值時,就丟棄這個mark,如果使用的是Direct模式創建的Buffer的話,就會減少中間緩衝直接使用DirectorBuffer來進行數據的存儲。
-
四.Java中的IO流(BIO):
分類
- 按照操作數據單位不同:位元組流(8bit),字符流(16bit)
- 按照數據流的流向不同:輸入流,輸出流
- 按照流的角色分為:
- 節點流:直接從數據源或目的地讀寫數據
-
處理流:不直接連接到數據源或目的地,而是連接在已存在的流(節點流或處理流)之上,通過對數據的處理為程序提 供更為強大的讀寫功能。
實現類:
寄語:我努力奔跑是為了追上那個曾經被寄予厚望的自己