1. 程式人生 > 實用技巧 >IO流--BIO/NIO/AIO

IO流--BIO/NIO/AIO

IO流

java.io

主要分為字元流和位元組流,字元流一般用於文字檔案,位元組流一般用於影象或其他檔案。

字元流包括了字元輸入流Reader和字元輸出流Writer;

位元組流包括了位元組輸入流InputStream和位元組輸出流OutputStream。

字元流和位元組流都有對應的緩衝流,位元組流也可以包裝為字元流,緩衝流有一個8kb的緩衝陣列,可以提高流的讀寫效率。除了緩衝流外還有過濾流FilterReader、字元陣列流CharArrayReader、位元組陣列流ByteArrayInputStream、檔案流FileInputStream。

在這裡插入圖片描述

同步/非同步/阻塞/非阻塞 IO

同步和非同步是通訊機制,阻塞和非阻塞是呼叫狀態。

  1. 同步和非同步是針對應用程式和核心的互動而言的同步/非同步是在時間上強調處理事情的結果/機會成本的兩種處理策略

    強調結果 意味著對結果的迫不急待,不過結果是正確的還是錯誤的,反正你要立即給我一個結果響應。(同步

    強調時間機會成本 意味著對等待結果浪費的時間極其難接受,而對結果並不是那麼急切,暫時不管結果(讓處理方處理完主動通知結果/自己空閒的時候主動去獲取結果)轉而去處理其他事情。(非同步

  2. 阻塞和非阻塞是針對於程序在訪問資料的時候,根據IO操作的就緒狀態來採取的不同方式,說白了是一種讀取或者寫入操作函式的實現方式,阻塞方式下讀取或者寫入函式將一直等待,而非阻塞方式下,讀取或者寫入函式會立即返回一個狀態值。

  3. 同步/非同步是巨集觀上(程序間通訊,通常表現為網路IO的處理上),阻塞/非阻塞是微觀上(程序內資料傳輸,通常表現為對本地IO的處理上);阻塞和非阻塞是同步/非同步的表現形式

由上描述基本可以總結一句簡短的話,**同步和非同步是目的,阻塞和非阻塞是實現方式。 **

具體解釋:

  • **同步IO:**是使用者執行緒發起IO請求後需要等待或輪詢核心IO操作完成後才能繼續執行。

    舉例:自己上街買衣服,自己親自幹這件事,別的事幹不了。

  • **非同步IO:**是使用者執行緒發起IO請求後可以繼續執行,當核心IO操作完成後會通知使用者執行緒,或呼叫使用者執行緒的回撥函式。

    舉例:告訴朋友自己合適衣服的尺寸,大小,顏色,委託朋友去買,然後自己可以去幹別的事。(使用非同步IO時,Java將IO讀寫委託給OS處理,需要將資料緩衝區地址和大小傳給OS)

  • **阻塞IO:**是IO操作需要徹底完成才能返回使用者空間。

    舉例:去公交站充值,發現這個時候,充值員不在(可能上廁所去了),然後我們就在這裡等待,一直等到充值員回來為止。

  • **非阻塞IO:**是IO操作呼叫後立即返回一個狀態值,無需等待IO操作徹底完成。

    舉例:銀行裡取款辦業務時,領取一張小票,領取完後我們自己可以玩玩手機,或者與別人聊聊天,當輪我們時,銀行的喇叭會通知,這時候我們就可以去了。

以上轉自原文:https://blog.csdn.net/u013851082/article/details/53942947

BIO(同步阻塞)

同步阻塞式IO,JDK1.4之前的IO模型。傳統的同步阻塞模型開發中,ServerSocket負責繫結IP地址,啟動監聽埠;Socket負責發起連線操作。連線成功後,雙方通過輸入和輸出流進行同步阻塞式通訊。 服務端提供IP和監聽埠,客戶端通過連線操作想服務端監聽的地址發起連線請求,通過三次握手連線,如果連線成功建立,雙方就可以通過套接字進行通訊。

簡單的描述一下BIO的服務端通訊模型:採用BIO通訊模型的服務端,通常由一個獨立的Acceptor執行緒負責監聽客戶端的連線,它接收到客戶端連線請求之後為每個客戶端建立一個新的執行緒進行鏈路處理沒處理完成後,通過輸出流返回應答給客戶端,執行緒銷燬。即典型的一請求一應答通宵模型。

傳統BIO模型圖
在這裡插入圖片描述

這種伺服器實現模式為一個連線請求對應一個執行緒,伺服器需要為每一個客戶端請求建立一個執行緒,如果這個連線不做任何事,會造成不必要的執行緒開銷。可以通過執行緒池改善,這種IO稱為偽非同步IO

即當有新的客戶端接入時,將客戶端的 Socket 封裝成一個Task(該任務實現java.lang.Runnable介面)投遞到後端的執行緒池中進行處理,JDK 的執行緒池維護一個訊息佇列和 N 個活躍執行緒,對訊息佇列中的任務進行處理。由於執行緒池可以設定訊息佇列的大小和最大執行緒數,因此,它的資源佔用是可控的,無論多少個客戶端併發訪問,都不會導致資源的耗盡和宕機。

偽非同步I/O通訊框架採用了執行緒池實現,因此避免了為每個請求都建立一個獨立執行緒造成的執行緒資源耗盡問題。不過因為它的底層任然是同步阻塞的BIO模型,因此無法從根本上解決問題。

在這裡插入圖片描述

BIO適用於連線數目少且伺服器資源多的場景。


NIO(同步非阻塞)

NIO是JDK1.4引入的同步非阻塞IO。伺服器實現模式為多個連線請求對應一個執行緒,客戶端連線請求會註冊到一個多路複用器Selector,Selector輪詢到連線有IO請求時才啟動一個執行緒處理。

適用於連線數目多且連線時間短的場景。

同步是指執行緒還是要不斷接收客戶端連線並處理資料,非阻塞是指如果一個管道沒有數,不需要等待,可以輪詢下一個管道。

核心元件:

  • **Selector:**多路複用器,輪詢檢查多個Channel的狀態,判斷註冊事件是否發生,即判斷Channel是否可處於可讀或可寫狀態。使用前需要將Channel註冊到Selector,註冊後會得到一個SelectionKey獲取Channel和Selector相關資訊。

  • **Channel:**雙向通道,替換了BIO中的Stream流,不能直接訪問資料,要通過Buffer來讀寫資料,也可以和其他Channel互動。

  • **Buffer:**緩衝區,本質是一塊可讀可寫資料的記憶體,來簡化資料讀寫。

    Buffer的三個重要屬性:

    • ==position==當寫資料到Buffer中時,position表示當前的位置。初始的position值為0。當一個byte、long等資料寫到Buffer後, position會向前移動到下一個可插入資料的 Buffer 單元。position 最大可為 capacity – 1。 當讀取資料時,也是從某個特定位置讀。當將 Buffer 從寫模式切換到讀模式,position會被重置為 0。當從Buffer的 position 處讀取資料時,position 向前移動到下一個可讀的位置。

    • limit本次讀寫的極限位置。在寫模式下,Buffer的limit表示最多能往 Buffer 裡寫多少資料。 寫模式下,limit 等於 Buffer 的 capacity 。 當切換Buffer到讀模式時, limit 表示*最多能讀到多少資料*。因此,當切換Buffer到讀模式時,limit 會被設定成寫模式下的 position 值。換句話說,你能讀到之前寫入的所有資料(limit被設定成已寫資料的數量,這個值在寫模式下就是 position )。

    • capacity 最大容量。 只能往裡寫 capacity 個 byte、long,char 等型別。一旦 Buffer 滿了,需要將其清空(通過讀資料或者清除資料)才能繼續寫資料往裡寫資料。

      img

    Buffer讀寫的四個步驟:

    • 寫入資料到Buffer

    • 呼叫==flip()==方法。

      flip()將寫轉為讀,底層實現原理把position置0,並把limit設為當前的position值。

    • 從Buffer中讀取資料

    • 呼叫clear()方法或者compact()方法。

      ==clear()==方法將都轉換為寫模式(用於讀完全資料的情況,把position置0,limit設為capacity)。

      ==compact()==將讀轉換為寫模式(用於存在未讀資料的情況,讓position指向未讀資料的下一個)。

    通道方向和Buffer方向相反,讀資料相當於向Buffer寫資料,寫資料相當於從Buffer讀。

在這裡插入圖片描述

在這裡插入圖片描述

在別的博主那看到的一個很形象的例子:

連線:https://blog.csdn.net/huangwenyi1010/article/details/75577091

非常形象的例項

小量的執行緒如何同時為大量連線服務呢,答案就是就緒選擇。這就好比到餐廳吃飯,每來一桌客人,都有一個服務員專門為你服務,從你到餐廳到結帳走人,這樣方式的好處是服務質量好,一對一的服務,VIP啊,可是缺點也很明顯,成本高,如果餐廳生意好,同時來100桌客人,就需要100個服務員,那老闆發工資的時候得心痛死了,這就是傳統的一個連線一個執行緒的方式。

老闆是什麼人啊,精著呢。這老闆就得捉摸怎麼能用10個服務員同時為100桌客人服務呢,老闆就發現,服務員在為客人服務的過程中並不是一直都忙著,客人點完菜,上完菜,吃著的這段時間,服務員就閒下來了,可是這個服務員還是被這桌客人佔用著,不能為別的客人服務,用華為領導的話說,就是工作不飽滿。那怎麼把這段閒著的時間利用起來呢。這餐廳老闆就想了一個辦法,讓一個服務員(前臺)專門負責收集客人的需求,登記下來,比如有客人進來了、客人點菜了,客人要結帳了,都先記錄下來按順序排好。每個服務員到這裡領一個需求,比如點菜,就拿著選單幫客人點菜去了。點好菜以後,服務員馬上回來,領取下一個需求,繼續為別人客人服務去了。這種方式服務質量就不如一對一的服務了,當客人資料很多的時候可能需要等待。但好處也很明顯,由於在客人正吃飯著的時候服務員不用閒著了,服務員這個時間內可以為其他客人服務了,原來10個服務員最多同時為10桌客人服務,現在可能為50桌,10客人服務了。

這種服務方式跟傳統的區別有兩個:

1、增加了一個角色,要有一個專門負責收集客人需求的人。NIO裡對應的就是Selector。

2、由阻塞服務方式改為非阻塞服務了,客人吃著的時候服務員不用一直侯在客人旁邊了。傳統的IO操作,比如read(),當沒有資料可讀的時候,執行緒一直阻塞被佔用,直到資料到來。NIO中沒有資料可讀時,read()會立即返回0,執行緒不會阻塞。

NIO中,客戶端建立一個連線後,先要將連線註冊到Selector,相當於客人進入餐廳後,告訴前臺你要用餐,前臺會告訴你你的桌號是幾號,然後你就可能到那張桌子坐下了,SelectionKey就是桌號。當某一桌需要服務時,前臺就記錄哪一桌需要什麼服務,比如1號桌要點菜,2號桌要結帳,服務員從前臺取一條記錄,根據記錄提供服務,完了再來取下一條。這樣服務的時間就被最有效的利用起來了。

工作原理

在這裡插入圖片描述

NIO和IO的主要區別

該部分轉自:https://blog.csdn.net/zengxiantao1994/article/details/88094910

IONIO
阻塞非阻塞
面向流面向緩衝流
Selector(選擇器)

面向流/面向緩衝流

Java IO和NIO之間第一個最大的區別是,IO是面向流的,NIO是面向緩衝區的。

IO/*面向流*意味著每次從流中讀一個或多個位元組,直至讀取所有位元組,它們沒有被快取在任何地方。此外,它不能前後移動流中的資料。如果需要前後移動從流中讀取的資料,需要先將它快取到一個緩衝區。

NIO的緩衝導向方法略有不同。資料讀取到一個它稍後處理的緩衝區,需要時可在緩衝區中前後移動。這就增加了處理過程中的靈活性。但是,還需要檢查是否該緩衝區中包含所有您需要處理的資料。而且,需確保當更多的資料讀入緩衝區時,不要覆蓋緩衝區裡尚未處理的資料。

阻塞/非阻塞

IO的各種流是*阻塞的*。這意味著,當一個執行緒呼叫read() 或 write() 時,該執行緒被阻塞,直到有一些資料被讀取,或資料完全寫入。該執行緒在此期間不能再幹任何事情了。

NIO非阻塞模式,使一個執行緒從某通道傳送請求讀取資料,但是它僅能得到目前可用的資料,如果目前沒有資料可用時,就什麼都不會獲取,而不是保持執行緒阻塞,所以直至資料變的可以讀取之前,該執行緒可以繼續做其他的事情。非阻塞寫也是如此。一個執行緒請求寫入一些資料到某通道,但不需要等待它完全寫入,這個執行緒同時可以去做別的事情。執行緒通常將非阻塞IO的空閒時間用於在其它通道上執行IO操作,所以一個單獨的執行緒現在可以管理多個輸入和輸出通道(channel)。

selector

NIO的選擇器允許一個單獨的執行緒來監視多個輸入通道,你可以註冊多個通道使用一個選擇器,然後使用一個單獨的執行緒來“選擇”通道:這些通道里已經有可以處理的輸入,或者選擇已準備寫入的通道。這種選擇機制,使得一個單獨的執行緒很容易來管理多個通道。


AIO(非同步非阻塞)

AIO是JDK7引入的非同步非阻塞IO,被稱為NIO.2,IO是對JDK1.4中提出的同步非阻塞I/O(NIO)的進一步增強。伺服器實現模式為一個有效請求對應一個執行緒,客戶端的IO請求都是由作業系統先完成IO操作後再通知伺服器應用來直接使用準備好的資料。

適用連線數目多且連線時間長的場景。

非同步是指伺服器端執行緒接收到客戶端管道後就交給底層處理IO通訊,可以自己做其他事情;

非阻塞是指客戶端有資料才會處理,處理好再通知伺服器。

jdk7主要增加了三個新的非同步通道:

  • AsynchronousFileChannel: 用於檔案非同步讀寫;
  • AsynchronousSocketChannel: 客戶端非同步socket;
  • AsynchronousServerSocketChannel: 伺服器非同步socket。

事情;

非阻塞是指客戶端有資料才會處理,處理好再通知伺服器。

jdk7主要增加了三個新的非同步通道:

  • AsynchronousFileChannel: 用於檔案非同步讀寫;
  • AsynchronousSocketChannel: 客戶端非同步socket;
  • AsynchronousServerSocketChannel: 伺服器非同步socket。

AIO的實現方式包括通過Future的get()方法進行阻塞式呼叫以及實現CompletionHandler介面,重寫請求成功的回撥方法completed和請求失敗回撥方法failed