一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|JAVA教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|JavaScript|易語言|

服務器之家 - 編程語言 - JAVA教程 - 快速了解Java中NIO核心組件

快速了解Java中NIO核心組件

2021-03-05 14:36default JAVA教程

這篇文章主要介紹了快速了解Java中NIO核心組件,涉及相關介紹及完整實例,具有一定借鑒價值,需要的朋友可以參考下。

背景知識

同步、異步、阻塞、非阻塞

首先,這幾個概念非常容易搞混淆,但nio中又有涉及,所以總結一下。

同步:api調用返回時調用者就知道操作的結果如何了(實際讀取/寫入了多少字節)。

異步:相對于同步,api調用返回時調用者不知道操作的結果,后面才會回調通知結果。

阻塞:當無數據可讀,或者不能寫入所有數據時,掛起當前線程等待。

非阻塞:讀取時,可以讀多少數據就讀多少然后返回,寫入時,可以寫入多少數據就寫入多少然后返回。

對于i/o操作,根據oracle官網的文檔,同步異步的劃分標準是“調用者是否需要等待i/o操作完成”,這個“等待i/o操作完成”的意思不是指一定要讀取到數據或者說寫入所有數據,而是指真正進行i/o操作時,比如數據在tcp/ip協議棧緩沖區和jvm緩沖區之間傳輸的這段時間,調用者是否要等待。

所以,我們常用的read()和write()方法都是同步i/o,同步i/o又分為阻塞和非阻塞兩種模式,如果是非阻塞模式,檢測到無數據可讀時,直接就返回了,并沒有真正執行i/o操作。

總結就是,java中實際上只有同步阻塞i/o、同步非阻塞i/o與異步i/o三種機制,我們下文所說的是前兩種,jdk1.7才開始引入異步i/o,那稱之為nio.2。

傳統io

我們知道,一個新技術的出現總是伴隨著改進和提升,javanio的出現亦如此。

傳統i/o是阻塞式i/o,主要問題是系統資源的浪費。比如我們為了讀取一個tcp連接的數據,調用inputstream的read()方法,這會使當前線程被掛起,直到有數據到達才被喚醒,那該線程在數據到達這段時間內,占用著內存資源(存儲線程棧)卻無所作為,也就是俗話說的占著茅坑不拉屎,為了讀取其他連接的數據,我們不得不啟動另外的線程。在并發連接數量不多的時候,這可能沒什么問題,然而當連接數量達到一定規模,內存資源會被大量線程消耗殆盡。另一方面,線程切換需要更改處理器的狀態,比如程序計數器、寄存器的值,因此非常頻繁的在大量線程之間切換,同樣是一種資源浪費。

隨著技術的發展,現代操作系統提供了新的i/o機制,可以避免這種資源浪費。基于此,誕生了javanio,nio的代表性特征就是非阻塞i/o。緊接著我們發現,簡單的使用非阻塞i/o并不能解決問題,因為在非阻塞模式下,read()方法在沒有讀取到數據時就會立即返回,不知道數據何時到達的我們,只能不停的調用read()方法進行重試,這顯然太浪費cpu資源了,從下文可以知道,selector組件正是為解決此問題而生。

javanio核心組件

1.channel

概念

javanio中的所有i/o操作都基于channel對象,就像流操作都要基于stream對象一樣,因此很有必要先了解channel是什么。以下內容摘自jdk1.8的文檔

achannelrepresentsanopenconnectiontoanentitysuchasahardwaredevice,afile,anetworksocket,oraprogramcomponentthatiscapableofperformingoneormoredistincti/ooperations,forexamplereadingorwriting.

從上述內容可知,一個channel(通道)代表和某一實體的連接,這個實體可以是文件、網絡套接字等。也就是說,通道是javanio提供的一座橋梁,用于我們的程序和操作系統底層i/o服務進行交互。

通道是一種很基本很抽象的描述,和不同的i/o服務交互,執行不同的i/o操作,實現不一樣,因此具體的有filechannel、socketchannel等。

通道使用起來跟stream比較像,可以讀取數據到buffer中,也可以把buffer中的數據寫入通道。

快速了解Java中NIO核心組件

當然,也有區別,主要體現在如下兩點:

一個通道,既可以讀又可以寫,而一個stream是單向的(所以分inputstream和outputstream)

通道有非阻塞i/o模式

實現

javanio中最常用的通道實現是如下幾個,可以看出跟傳統的i/o操作類是一一對應的。

filechannel:讀寫文件

datagramchannel:udp協議網絡通信

socketchannel:tcp協議網絡通信

serversocketchannel:監聽tcp連接

2.buffer

nio中所使用的緩沖區不是一個簡單的byte數組,而是封裝過的buffer類,通過它提供的api,我們可以靈活的操縱數據,下面細細道來。

與java基本類型相對應,nio提供了多種buffer類型,如bytebuffer、charbuffer、intbuffer等,區別就是讀寫緩沖區時的單位長度不一樣(以對應類型的變量為單位進行讀寫)。

buffer中有3個很重要的變量,它們是理解buffer工作機制的關鍵,分別是

capacity(總容量)

position(指針當前位置)

limit(讀/寫邊界位置)

buffer的工作方式跟c語言里的字符數組非常的像,類比一下,capacity就是數組的總長度,position就是我們讀/寫字符的下標變量,limit就是結束符的位置。buffer初始時3個變量的情況如下圖

快速了解Java中NIO核心組件

在對buffer進行讀/寫的過程中,position會往后移動,而limit就是position移動的邊界。由此不難想象,在對buffer進行寫入操作時,limit應當設置為capacity的大小,而對buffer進行讀取操作時,limit應當設置為數據的實際結束位置。(注意:將buffer數據寫入通道是buffer讀取操作,從通道讀取數據到buffer是buffer寫入操作)

在對buffer進行讀/寫操作前,我們可以調用buffer類提供的一些輔助方法來正確設置position和limit的值,主要有如下幾個

flip():設置limit為position的值,然后position置為0。對buffer進行讀取操作前調用。

rewind():僅僅將position置0。一般是在重新讀取buffer數據前調用,比如要讀取同一個buffer的數據寫入多個通道時會用到。

clear():回到初始狀態,即limit等于capacity,position置0。重新對buffer進行寫入操作前調用。

compact():將未讀取完的數據(position與limit之間的數據)移動到緩沖區開頭,并將position設置為這段數據末尾的下一個位置。其實就等價于重新向緩沖區中寫入了這么一段數據。

然后,看一個實例,使用filechannel讀寫文本文件,通過這個例子驗證通道可讀可寫的特性以及buffer的基本用法(注意filechannel不能設置為非阻塞模式)。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
filechannel channel = new randomaccessfile("test.txt", "rw").getchannel();
channel.position(channel.size());
// 移動文件指針到末尾(追加寫入)
bytebuffer bytebuffer = bytebuffer.allocate(20);
// 數據寫入buffer
bytebuffer.put("你好,世界!\n".getbytes(standardcharsets.utf_8));
// buffer -> channel
bytebuffer.flip();
while (bytebuffer.hasremaining()) {
    channel.write(bytebuffer);
}
channel.position(0);
// 移動文件指針到開頭(從頭讀取)
charbuffer charbuffer = charbuffer.allocate(10);
charsetdecoder decoder = standardcharsets.utf_8.newdecoder();
// 讀出所有數據
bytebuffer.clear();
while (channel.read(bytebuffer) != -1 || bytebuffer.position() > 0) {
    bytebuffer.flip();
    // 使用utf-8解碼器解碼
    charbuffer.clear();
    decoder.decode(bytebuffer, charbuffer, false);
    system.out.print(charbuffer.flip().tostring());
    bytebuffer.compact();
    // 數據可能有剩余
}
channel.close();

這個例子中使用了兩個buffer,其中 bytebuffer 作為通道讀寫的數據緩沖區,charbuffer 用于存儲解碼后的字符。clear() 和 flip() 的用法正如上文所述,需要注意的是最后那個 compact() 方法,即使 charbuffer 的大小完全足以容納 bytebuffer 解碼后的數據,這個 compact() 也必不可少,這是因為常用中文字符的utf-8編碼占3個字節,因此有很大概率出現在中間截斷的情況,請看下圖:

快速了解Java中NIO核心組件

當 decoder 讀取到緩沖區末尾的 0xe4 時,無法將其映射到一個 unicode,decode()方法第三個參數 false 的作用就是讓 decoder 把無法映射的字節及其后面的數據都視作附加數據,因此 decode() 方法會在此處停止,并且 position 會回退到 0xe4 的位置。如此一來, 緩沖區中就遺留了“中”字編碼的第一個字節,必須將其 compact 到前面,以正確的和后序數據拼接起來。關于字符編碼,大家可以參閱ansi,unicode,bmp,utf等編碼概念實例講解

btw,例子中的charsetdecoder也是javanio的一個新特性,所以大家應該發現了一點哈,nio的操作是面向緩沖區的(傳統i/o是面向流的)。

至此,我們了解了channel與buffer的基本用法。接下來要說的是讓一個線程管理多個channel的重要組件。

3.selector

selector是什么

selector(選擇器)是一個特殊的組件,用于采集各個通道的狀態(或者說事件)。我們先將通道注冊到選擇器,并設置好關心的事件,然后就可以通過調用select()方法,靜靜地等待事件發生。

通道有如下4個事件可供我們監聽:

accept:有可以接受的連接

connect:連接成功

read:有數據可讀

write:可以寫入數據了

為什么要用selector

前文說了,如果用阻塞i/o,需要多線程(浪費內存),如果用非阻塞i/o,需要不斷重試(耗費cpu)。selector的出現解決了這尷尬的問題,非阻塞模式下,通過selector,我們的線程只為已就緒的通道工作,不用盲目的重試了。比如,當所有通道都沒有數據到達時,也就沒有read事件發生,我們的線程會在select()方法處被掛起,從而讓出了cpu資源。

使用方法

如下所示,創建一個selector,并注冊一個channel。

注意:要將channel注冊到selector,首先需要將channel設置為非阻塞模式,否則會拋異常。

?
1
2
3
selector selector = selector.open();
channel.configureblocking(false);
selectionkey key = channel.register(selector, selectionkey.op_read);

register()方法的第二個參數名叫“interest set”,也就是你所關心的事件集合。如果你關心多個事件,用一個“按位或運算符”分隔,比如

?
1
selectionkey.op_read | selectionkey.op_write

這種寫法一點都不陌生,支持位運算的編程語言里都這么玩,用一個整型變量可以標識多種狀態,它是怎么做到的呢,其實很簡單,舉個例子,首先預定義一些常量,它們的值(二進制)如下

快速了解Java中NIO核心組件

可以發現,它們值為1的位都是錯開的,因此對它們進行按位或運算之后得出的值就沒有二義性,可以反推出是由哪些變量運算而來。怎么判斷呢,沒錯,就是“按位與”運算。比如,現在有一個狀態集合變量值為0011,我們只需要判斷“0011&op_read”的值是1還是0就能確定集合是否包含op_read狀態。

然后,注意register()方法返回了一個selectionkey的對象,這個對象包含了本次注冊的信息,我們也可以通過它修改注冊信息。從下面完整的例子中可以看到,select()之后,我們也是通過獲取一個selectionkey的集合來獲取到那些狀態就緒了的通道。

一個完整實例

概念和理論的東西闡述完了(其實寫到這里,我發現沒寫出多少東西,好尷尬(⊙?⊙)),看一個完整的例子吧。

這個例子使用javanio實現了一個單線程的服務端,功能很簡單,監聽客戶端連接,當連接建立后,讀取客戶端的消息,并向客戶端響應一條消息。

需要注意的是,我用字符‘\0′(一個值為0的字節)來標識消息結束。

單線程server

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
public class nioserver {
    public static void main(string[] args) throws ioexception {
        // 創建一個selector
        selector selector = selector.open();
        // 初始化tcp連接監聽通道
        serversocketchannel listenchannel = serversocketchannel.open();
        listenchannel.bind(new inetsocketaddress(9999));
        listenchannel.configureblocking(false);
        // 注冊到selector(監聽其accept事件)
        listenchannel.register(selector, selectionkey.op_accept);
        // 創建一個緩沖區
        bytebuffer buffer = bytebuffer.allocate(100);
        while (true) {
            selector.select();
            //阻塞,直到有監聽的事件發生
            iterator<selectionkey> keyiter = selector.selectedkeys().iterator();
            // 通過迭代器依次訪問select出來的channel事件
            while (keyiter.hasnext()) {
                selectionkey key = keyiter.next();
                if (key.isacceptable()) {
                    // 有連接可以接受
                    socketchannel channel = ((serversocketchannel) key.channel()).accept();
                    channel.configureblocking(false);
                    channel.register(selector, selectionkey.op_read);
                    system.out.println("與【" + channel.getremoteaddress() + "】建立了連接!");
                } else if (key.isreadable()) {
                    // 有數據可以讀取
                    buffer.clear();
                    // 讀取到流末尾說明tcp連接已斷開,
                    // 因此需要關閉通道或者取消監聽read事件
                    // 否則會無限循環
                    if (((socketchannel) key.channel()).read(buffer) == -1) {
                        key.channel().close();
                        continue;
                    }
                    // 按字節遍歷數據
                    buffer.flip();
                    while (buffer.hasremaining()) {
                        byte b = buffer.get();
                        if (b == 0) {
                            // 客戶端消息末尾的\0
                            system.out.println();
                            // 響應客戶端
                            buffer.clear();
                            buffer.put("hello, client!\0".getbytes());
                            buffer.flip();
                            while (buffer.hasremaining()) {
                                ((socketchannel) key.channel()).write(buffer);
                            }
                        } else {
                            system.out.print((char) b);
                        }
                    }
                }
                // 已經處理的事件一定要手動移除
                keyiter.remove();
            }
        }
    }
}

client

這個客戶端純粹測試用,為了看起來不那么費勁,就用傳統的寫法了,代碼很簡短。

要嚴謹一點測試的話,應該并發運行大量client,統計服務端的響應時間,而且連接建立后不要立刻發送數據,這樣才能發揮出服務端非阻塞i/o的優勢。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class client {
    public static void main(string[] args) throws exception {
        socket socket = new socket("localhost", 9999);
        inputstream is = socket.getinputstream();
        outputstream os = socket.getoutputstream();
        // 先向服務端發送數據
        os.write("hello, server!\0".getbytes());
        // 讀取服務端發來的數據
        int b;
        while ((b = is.read()) != 0) {
            system.out.print((char) b);
        }
        system.out.println();
        socket.close();
    }
}

總結

以上就是本文關于快速了解java中nio核心組件的全部內容,希望對大家有所幫助。感興趣的朋友可以繼續參閱本站其他相關內容,如有不足之處,歡迎留言指出。感謝朋友們對本站的支持!

原文鏈接:http://www.php.cn/java-article-374697.html

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 大象传媒2021秘密入口 | 国产精品午夜性视频网站 | 99精品视频免费观看 | 女主被男主为催奶药h | 男人插女人软件 | 无人区尖叫之夜美女姐姐视频 | 大陆黄色片 | 日韩欧美一区二区三区 | 国产精品色爱综合网 | 亚洲AV无码国产精品色午夜情 | 日本动漫啪啪动画片mv | 日本大学生xxxxx69泡妞 | 男人天堂色男人 | 美女靠逼免费网站 | 操老肥熟| 国产精品不卡高清在线观看 | 国产成人影院一区二区 | 91精品综合久久久久久五月天 | 亚洲国产美女精品久久久久 | 韩国三级年轻小的胰子完整 | 国内精品中文字幕 | 亚洲国产美女精品久久久久 | 99手机在线视频 | 日本久久影视 | 双性肉文高h | 美女扒开腿让男人桶爽动态图片 | 日本高清中文字幕 | 青草热久精品视频在线观看 | 国产精品亚洲专区在线播放 | 国产成人精品在线 | 大伊香蕉在线精品不卡视频 | 欧美成人v视频免费看 | 免费一级生活片 | 欧美精品99久久久久久人 | 91久久精品视频 | 18日本人 | 深夜在线网站 | 日韩亚洲一区中文字幕在线 | 精品小视频在线观看 | 亚洲国产成人精品不卡青青草原 | 国产成人精品男人的天堂538 |