1 NIO簡介
? ? ? ? 在1.4版本之前,Java NIO類庫是阻塞IO,從1.4版本開始,引進了新的異步IO庫,被稱為Java New IO類庫,簡稱為Java NIO。New IO類庫的目的 就是要讓Java支持非阻塞IO。
? ? ? ? Java NIO類庫包含三個核心組件:
? ? ? ? 1、Channel(通道)
? ? ? ? 2、Buffer(緩沖區(qū))
? ? ? ? 3、Selector(選擇器)
? ? ? ? 理解了上一章高并發(fā)IO底層原理,大家會馬上識別出來Java NIO屬于第三種模型——IO多路復用模型。只不過,Java NIO組件提供了統(tǒng)一的API,為大家屏蔽了底層的操作系統(tǒng)的差異。
? ? ? ? 本章,會對上面3個組件展開詳細介紹。首先看一下Java NIO和OIO的簡單對比。
1.1 NIO和OIO的對比
? ? ? ? 在Java中,NIO和OIO的區(qū)別主要體現(xiàn)在三個地方:
? ? ? ? 1、OIO是面向流的,NIO是面向緩沖區(qū)的。在一般的OIO操作中,面向字節(jié)流或字符流的IO操作總是以流式的方式順序地從一個流(Stream)中讀取一個或多個字節(jié),因此,我們不能隨意改變讀取指針的位置。在NIO操作中則不同,NIO中引入了Channel和Buffer的概念。面向緩沖區(qū)的讀取和寫入只需要從通道讀取數(shù)據(jù)到緩沖區(qū)中,或?qū)?shù)據(jù)從緩沖區(qū)寫入到通道中。NIO不像OIO那樣順序操作,它可以隨意讀取Buffer中任意位置的數(shù)據(jù)。
? ? ? ? 2、OIO的操作是阻塞的,而NIO是非阻塞的。OIO操作時,例如調(diào)用一個read方法讀取一個文件的內(nèi)容,調(diào)用read的線程就會被阻塞,直到read操作完成。在NIO模式中,當調(diào)用read方法時,如果此時有數(shù)據(jù),則read讀取數(shù)據(jù)并返回,如果此時沒有數(shù)據(jù),則read也返回直接返回,而不會阻塞當前線程。
? ? ? ? 3、OIO沒有選擇器(Selector)的概念,而NIO有選擇器的概念。NIO的實現(xiàn)是基于底層選擇器的系統(tǒng)調(diào)用的,所以NIO需要底層操作系統(tǒng)提供支持,而OIO不需要選擇器。
1.2 通道
? ? ? ? 在OIO中,同一個網(wǎng)絡連接會關聯(lián)兩個流:一個是輸入流(Input Stream),另一個是輸出流(Output Stream)。Java應用程序通過這兩個流不斷地進行輸入和輸出的操作。
? ? ? ? 在NIO中,一個網(wǎng)絡連接使用一個通道表示,所有NIO的IO操作都是通過連接通道完成的。一個通道類似于OIO中兩個流的結合體,既可以從通道讀取數(shù)據(jù),也可以向通道寫入數(shù)據(jù)。
1.3 選擇器
? ? ? ? 首先回顧一下前面介紹的基礎知識——IO多路復用(高并發(fā)IO底層原理)指的是一個進程/線程可以同時監(jiān)視多個文件描述符(含socket連接),一旦其中的一個或多個文件描述符可讀或者可寫,該監(jiān)聽進程/線程就能夠進行IO就緒事件的查詢。
? ? ? ? 在Java應用層面,如何實現(xiàn)對多個文件描述符的監(jiān)視呢?需要用到一個非常重要的Java NIO組件——選擇器。選擇器可以理解為一個IO事件的監(jiān)聽與查詢器。通過選擇器,一個線程可以查詢多個通道的IO事件的就緒狀態(tài)。
? ? ? ? 從編程實現(xiàn)維度來說,IO多路復用變成的第一步是把通道注冊到選擇器中,第二步是通過選擇器所提供的事件查詢(select)方法來查詢這些注冊的通道是否有已經(jīng)就緒的IO事件(例如可讀、可寫、網(wǎng)絡連接完成等)。
? ? ? ? 由于一個選擇器只需要一個線程進行監(jiān)控,因此我們可以很簡單地使用一個線程,通過選擇器去管理多個連接通道。
? ? ? ? 與OIO相比,NIO使用選擇器的最大優(yōu)勢就是系統(tǒng)開銷小。系統(tǒng)不必為每一個網(wǎng)絡連接(文件描述符)創(chuàng)建進程/線程,從而大大減少了系統(tǒng)的開銷??傊粋€線程負責多個連接通道的IO處理是非常高效的,這種高效來自Java的選擇器組件Selector及其底層的操作系統(tǒng)IO多路復用技術的支持。
1.4 緩沖區(qū)
? ? ? ? 應用程序與通道的交互主要是進行數(shù)據(jù)的讀取和寫入。為了完成NIO的非阻塞讀寫操作,NIO準備了第三個重要的組件——Buffer。所謂通道的讀取,就是將數(shù)據(jù)從通道讀取到緩沖區(qū)中;通道的寫入,就是將數(shù)據(jù)從緩沖區(qū)寫入通道中。緩沖區(qū)的使用是面向流進行讀寫操作的OIO所沒有的,也就是NIO非阻塞的重要前提和基礎之一。
2 Buffer類
? ? ? ? Buffer類是一個抽象類,對應于Java的主要數(shù)據(jù)類型。在NIO中,有8種緩沖區(qū)類,分別是ByteBuffer、CharBuffer、DoubleBuffer、FloatBuffer、IntBuffer、LongBuffer、ShortBuffer、MappedByteBuffer。前7種Buffer類型覆蓋了能在IO中傳輸?shù)乃蠮ava基本數(shù)據(jù)類型,第8種類型是一種專門用于內(nèi)存映射的ByteBuffer類型。不同的Buffer子類可以操作的數(shù)據(jù)類型能夠通過名稱進行判斷,比如IntBuffer只能操作Integer類型的對象。
? ? ? ? 實際上,使用最多的是ByteBuffer(二進制字節(jié)緩沖區(qū))類型,后面的章節(jié)會看到它的具體使用。
2.1 Buffer類的重要屬性
? ? ? ? Buffer的子類會擁有一塊內(nèi)存,作為數(shù)據(jù)的讀寫緩沖區(qū),但是讀寫緩沖區(qū)并沒有定義在Buffer基類中,而是定義在具體的子類中。例如,ByteBuffer子類就擁有一個byte[]類型的數(shù)組成員
final byte[] hb;
可以作為自己的讀寫緩沖區(qū),數(shù)據(jù)的元素類型與Buffer子類的操作類型相對應。為了記錄讀寫的狀態(tài)和位置,Buffer類額外提供了一些重要的屬性,其中有三個重要的成員屬性:capacity(容量)、position(讀寫位置)和limit(讀寫的限制)。
? ? ? ? 1、capacity屬性
? ? ? ? Buffer類的capacity屬性表示內(nèi)存容量的大小。一旦寫入的對象數(shù)量超過了capacity,緩沖區(qū)就滿了,不能在寫入。capacity屬性一旦初始化,就不能再改變。原因是什么呢?Buffer類的對象在初始化時會按照capacity分配內(nèi)部數(shù)組的內(nèi)存,在數(shù)據(jù)內(nèi)存分配好之后,它的大小就不能改變了。前面講到,Buffer類是一個抽象類,Java不能直接用來創(chuàng)建對象。在具體使用的時候,必須使用Buffer的某個子類,例如DoubleBuffer子類,該子類能寫入的數(shù)據(jù)類型是double,如果在創(chuàng)建實例時,其capacity是100,那么最多可以寫入100個double類型的數(shù)據(jù)。
? ? ? ? 【注意】capacity不是指內(nèi)部的內(nèi)存塊byte[]數(shù)組的字節(jié)數(shù)量,而是指能寫入的數(shù)據(jù)對象的最大限制數(shù)量。
? ? ? ? 2、position屬性
? ? ? ? position表示當前的位置。position屬性的值與緩沖區(qū)的讀寫模式有關,在不同的模式下,position屬性值的含義是不同的,在緩沖區(qū)進行讀寫的模式改變時,position值會進行相應的調(diào)整。
? ? ? ? 在寫模式下,position值的變化規(guī)則如下:
? ? ? ? (1)在剛進入寫模式時,position值為0,表示當前的寫入位置從頭開始。
? ? ? ? (2)每當一個數(shù)據(jù)寫到緩沖區(qū)后,position就會向后移動到下一個可寫的位置。
? ? ? ? (3)初始的position值為0,最大可寫值為limit-1。當position值達到limit時,緩沖區(qū)就已經(jīng)無可用空間寫入了。
? ? ? ? 在讀模式下,position值的變化規(guī)則如下:
? ? ? ? (1)當緩沖區(qū)剛開始進入讀模式時,position會被重置為0.
? ? ? ? (2)當從緩沖區(qū)讀取時,也是從position位置開始讀。讀取數(shù)據(jù)后,position向前移動到下一個可讀的位置。
? ? ? ? (3)在讀模式下,limit表示可讀數(shù)據(jù)的上線。position的最大值為最大可讀上線limit,當position達到limit時表示緩沖區(qū)已經(jīng)無數(shù)據(jù)可讀。
? ? ? ? Buffer的讀寫模式具體如何切換呢?當新建了一個緩沖區(qū)實例時,緩沖區(qū)處于寫模式,這時可以寫入數(shù)據(jù)。在數(shù)據(jù)寫入完成后,如果要從緩沖區(qū)讀取數(shù)據(jù),就要進行模式的切換,可以調(diào)用flip()方法將緩沖區(qū)變成讀模式,flip為翻轉的意思。再從寫模式到讀模式的翻轉過程中,position和limit屬性值會進行調(diào)整,具體的規(guī)則是:
? ? ? ? (1)limit屬性被設置成寫模式時的position值,表示可以讀取的最大數(shù)據(jù)位置。
? ? ? ? (2)position由原來的寫入位置變成新的可讀位置,也就是0,表示可以從頭開始讀。
? ? ? ? 3、limit屬性
? ? ? ? limit屬性表示可以寫入或讀取的數(shù)據(jù)最大上限,其屬性值的具體含義也與緩沖區(qū)的讀寫模式有關。在不同的模式下,limit值的含義是不同的,具體分為以下兩種情況。
? ? ? ? (1)在寫模式下,limit屬性值的含義為可以寫入的數(shù)據(jù)最大上限。在剛進入寫模式時,limit的值會被設置成緩沖區(qū)的capacity值,表示可以一直將緩沖區(qū)的容量寫滿。
? ? ? ? (2)在讀模式下,limit值的含義為最大能從緩沖區(qū)讀取多少數(shù)據(jù)。
? ? ? ? 一般來說,在進行緩沖區(qū)操作時是先寫入再讀取。當緩沖區(qū)寫入完成后,就可以開始從Buffer讀取數(shù)據(jù),調(diào)用flip()方法,這時limit的值也會進行調(diào)整。具體如何調(diào)整呢?將寫模式下的position值設置成讀模式下的limit值,也就是說,將之前寫入的最大數(shù)量作為可以讀取數(shù)據(jù)的上限值。
? ? ? ? Buffer在翻轉時的屬性值調(diào)整主要涉及position,limit兩個屬性,但這種調(diào)整比較微妙,不太好理解,下面舉例說明:
? ? ? ? 首先,創(chuàng)建緩沖區(qū)。新創(chuàng)建的緩沖區(qū)處于寫模式,其position值為0,limit值為最大容量capacity。
? ? ? ? 然后,向緩沖區(qū)寫數(shù)據(jù)。每寫入一個數(shù)據(jù),position向后面移動一個位置,也就是position的值加1。這里假定寫入了5個數(shù),當寫入完成后,position的值為5.
? ? ? ? 最后,使用flip()方法將緩沖區(qū)切換到讀模式。limit的值會先被設置成寫模式的position值,所以新的limit的值是5,表示可以讀取數(shù)據(jù)的最大上限是5.之后調(diào)整position值,新的position會被重置為0,表示可以從0開始讀。
? ? ? ? 緩沖區(qū)切換到讀模式后就可以從緩沖區(qū)讀取數(shù)據(jù)了,一直到緩沖區(qū)的數(shù)據(jù)讀取完畢。
? ? ? ? 除了以上capacity、limit、position三個重要的成員屬性外,Buffer還有一個比較重要的標記屬性:mark(標記)屬性。該屬性的大致作用是:在緩沖區(qū)操作過程中,可以將當前的position值臨時存入mark屬性中,需要的時候,再從mark中取出暫存的標記值,恢復到position屬性中,重新從position位置開始處理。
? ? ? ? 下面用表總結一下Buffer類的四個重要屬性。
?文章來源地址http://www.zghlxwxcb.cn/news/detail-806570.html
? ? ? ?
? ?文章來源:http://www.zghlxwxcb.cn/news/detail-806570.html
?
到了這里,關于Java NIO (一)簡介的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!