Java IO/NIO
一、常見IO模型
1.1 阻塞 IO 模型
最傳統的一種 IO 模型,即在讀寫資料過程中會發生阻塞現象。當用戶執行緒發出 IO 請求之後,核心會去檢視資料是否就緒,如果沒有就緒就會等待資料就緒,而使用者執行緒就會處於阻塞狀態,使用者執行緒交出 CPU。當資料就緒之後,核心會將資料拷貝到使用者執行緒,並返回結果給使用者執行緒,使用者執行緒才解除 block 狀態。典型的阻塞 IO 模型的例子為:data = socket.read();如果資料沒有就緒,就會一直阻塞在 read 方法。1.2非阻塞 IO 模型
當用戶執行緒發起一個 read 操作後,並不需要等待,而是馬上就得到了一個結果。如果結果是一個error 時,它就知道資料還沒有準備好,於是它可以再次傳送 read 操作。一旦核心中的資料準備好了,並且又再次收到了使用者執行緒的請求,那麼它馬上就將資料拷貝到了使用者執行緒,然後返回。所以事實上,在非阻塞 IO 模型中,使用者執行緒需要不斷地詢問核心資料是否就緒
while(true){ data = socket.read(); if(data!= error){ 處理資料 break; } }
但是對於非阻塞 IO 就有一個非常嚴重的問題,在 while 迴圈中需要不斷地去詢問核心資料是否就緒,這樣會導致 CPU 佔用率非常高,因此一般情況下很少使用 while 迴圈這種方式來讀取資料。
1.3多路複用 IO 模型
多路複用 IO 模型是目前使用得比較多的模型。Java NIO 實際上就是多路複用 IO
另外多路複用 IO 為何比非阻塞 IO 模型的效率高是因為在非阻塞 IO 中,不斷地詢問 socket 狀態時通過使用者執行緒去進行的,而在多路複用 IO 中,輪詢每個 socket 狀態是核心在進行的,這個效率要比使用者執行緒要高的多。
不過要注意的是,多路複用 IO 模型是通過輪詢的方式來檢測是否有事件到達,並且對到達的事件逐一進行響應。因此對於多路複用 IO 模型來說,一旦事件響應體很大,那麼就會導致後續的事件遲遲得不到處理,並且會影響新的事件輪詢。1.4訊號驅動 IO 模型
在訊號驅動 IO 模型中,當用戶執行緒發起一個 IO 請求操作,會給對應的 socket 註冊一個訊號函式,然後使用者執行緒會繼續執行,當核心資料就緒時會發送一個訊號給使用者執行緒,使用者執行緒接收到訊號之後,便在訊號函式中呼叫 IO 讀寫操作來進行實際的 IO 請求操作。1.5非同步 IO 模型
非同步 IO 模型才是最理想的 IO 模型,在非同步 IO 模型中,當使用者執行緒發起 read 操作之後,立刻就可以開始去做其它的事。而另一方面,從核心的角度,當它受到一個 asynchronous read 之後,它會立刻返回,說明 read 請求已經成功發起了,因此不會對使用者執行緒產生任何 block。然後,核心會等待資料準備完成,然後將資料拷貝到使用者執行緒,當這一切都完成之後,核心會給使用者執行緒傳送一個訊號,告訴它 read 操作完成了。也就說使用者執行緒完全不需要實際的整個 IO 操作是如何進行的,只需要先發起一個請求,當接收核心返回的成功訊號時表示 IO 操作已經完成,可以直接去使用資料了。
也就說在非同步 IO 模型中,IO 操作的兩個階段都不會阻塞使用者執行緒,這兩個階段都是由核心自動完成,然後傳送一個訊號告知使用者執行緒操作已完成。使用者執行緒中不需要再次呼叫 IO 函式進行具體的讀寫。這點是和訊號驅動模型有所不同的,在訊號驅動模型中,當用戶執行緒接收到訊號表示資料已經就緒,然後需要使用者執行緒呼叫 IO 函式進行實際的讀寫操作;而在非同步 IO 模型中,收到訊號表示 IO 操作已經完成,不需要再在使用者執行緒中呼叫 IO 函式進行實際的讀寫操作。 注意,非同步 IO 是需要作業系統的底層支援,在 Java 7 中,提供了 Asynchronous IO。二、JAVA IO 包
三、JAVA NIO
NIO 主要有三大核心部分:Channel(通道),Buffer(緩衝區), Selector。傳統 IO 基於位元組流和字元流進行操作,而 NIO 基於 Channel 和 Buffer(緩衝區)進行操作,資料總是從通道讀取到緩衝區中,或者從緩衝區寫入到通道中。Selector(選擇區)用於監聽多個通道的事件(比如:連線開啟,資料到達)。因此,單個執行緒可以監聽多個數據通道。
NIO 和傳統 IO 之間第一個最大的區別是,IO 是面向流的,NIO 是面向緩衝區的。
3.1NIO 的緩衝區
Java IO 面向流意味著每次從流中讀一個或多個位元組,直至讀取所有位元組,它們沒有被快取在任何地方。此外,它不能前後移動流中的資料。如果需要前後移動從流中讀取的資料,需要先將它快取到一個緩衝區。NIO 的緩衝導向方法不同。資料讀取到一個它稍後處理的緩衝區,需要時可在緩衝區中前後移動。這就增加了處理過程中的靈活性。但是,還需要檢查是否該緩衝區中包含所有您需要處理的資料。而且,需確保當更多的資料讀入緩衝區時,不要覆蓋緩衝區裡尚未處理的資料。3.2NIO 的非阻塞
IO 的各種流是阻塞的。這意味著,當一個執行緒呼叫 read() 或 write()時,該執行緒被阻塞,直到有一些資料被讀取,或資料完全寫入。該執行緒在此期間不能再幹任何事情了。 NIO 的非阻塞模式,使一個執行緒從某通道傳送請求讀取資料,但是它僅能得到目前可用的資料,如果目前沒有資料可用時,就什麼都不會獲取。而不是保持執行緒阻塞,所以直至資料變的可以讀取之前,該執行緒可以繼續做其他的事情。 非阻塞寫也是如此。一個執行緒請求寫入一些資料到某通道,但不需要等待它完全寫入,這個執行緒同時可以去做別的事情。 執行緒通常將非阻塞 IO 的空閒時間用於在其它通道上執行 IO 操作,所以一個單獨的執行緒現在可以管理多個輸入和輸出通道(channel)。3.3Channel
首先說一下 Channel,國內大多翻譯成“通道”。Channel 和 IO 中的 Stream(流)是差不多一個等級的。只不過 Stream 是單向的,譬如:InputStream, OutputStream,而 Channel 是雙向的,既可以用來進行讀操作,又可以用來進行寫操作。 NIO 中的 Channel 的主要實現有: 1. FileChannel 2. DatagramChannel 3. SocketChannel 4. ServerSocketChannel 這裡看名字就可以猜出個所以然來:分別可以對應檔案 IO、UDP 和 TCP(Server 和 Client)。 下面演示的案例基本上就是圍繞這 4 個型別的 Channel 進行陳述的。3.4Buffer
Buffer,故名思意,緩衝區,實際上是一個容器,是一個連續陣列。Channel 提供從檔案、網路讀取資料的渠道,但是讀取或寫入的資料都必須經由 Buffer。上面的圖描述了從一個客戶端向服務端傳送資料,然後服務端接收資料的過程。客戶端傳送資料時,必須先將資料存入 Buffer 中,然後將 Buffer 中的內容寫入通道。服務端這邊接收資料必須通過 Channel 將資料讀入到 Buffer 中,然後再從 Buffer 中取出資料來處理。
在 NIO 中,Buffer 是一個頂層父類,它是一個抽象類,常用的 Buffer 的子類有:ByteBuffer、IntBuffer、 CharBuffer、 LongBuffer、 DoubleBuffer、FloatBuffer、ShortBuffer