Java?NIO中四大核心組件的使用詳解
Java NIO(New IO)是Java 1.4版本中引入的一套全新的IO處理機(jī)制,與之前的傳統(tǒng)IO相比,NIO具有更高的可擴(kuò)展性和靈活性,特別是在網(wǎng)絡(luò)編程和高并發(fā)場(chǎng)景下,表現(xiàn)得更為出色。
NIO提供了四個(gè)核心組件:Channel、Buffer、Selector和SelectionKey,通過(guò)它們的協(xié)同配合,實(shí)現(xiàn)數(shù)據(jù)的讀寫(xiě)和同步、非同步IO操作。本文將從基礎(chǔ)概念、核心組件、使用方法等方面全面詳細(xì)地介紹Java NIO,總字?jǐn)?shù)約8000字。
一、基礎(chǔ)概念
1.1 IO和NIO的區(qū)別
Java IO和NIO的主要區(qū)別在于兩者的處理方式不同。Java IO是面向流(Stream)的,它將輸入輸出數(shù)據(jù)直接傳輸?shù)侥繕?biāo)設(shè)備或文件中,以流的形式進(jìn)行讀寫(xiě);而NIO則是面向緩沖區(qū)(Buffer)的,它將會(huì)使用緩存去管理數(shù)據(jù),使得讀寫(xiě)操作更加快速和靈活。
特別是在網(wǎng)絡(luò)編程和高并發(fā)場(chǎng)景下,Java NIO表現(xiàn)得更為出色。Java IO在進(jìn)行網(wǎng)絡(luò)通信時(shí),每個(gè)客戶(hù)端連接都需要?jiǎng)?chuàng)建一個(gè)線程來(lái)進(jìn)行處理,這樣會(huì)導(dǎo)致系統(tǒng)資源的浪費(fèi)。Java NIO則只需要一個(gè)線程就可以完成對(duì)多個(gè)客戶(hù)端連接的處理,大大減少系統(tǒng)資源的占用。
1.2 緩沖區(qū)
緩沖區(qū)是Java NIO中一個(gè)非常重要的概念,它是用來(lái)存儲(chǔ)IO操作的數(shù)據(jù)的一段連續(xù)區(qū)域。緩沖區(qū)可以在內(nèi)存中創(chuàng)建,并可以通過(guò)通道(Channel)進(jìn)行讀寫(xiě)操作,也可以作為參數(shù)傳遞給其他方法。除此之外,緩沖區(qū)還有特定的類(lèi)型,例如ByteBuffer、CharBuffer、IntBuffer等。
不同類(lèi)型的緩沖區(qū)都包含以下幾個(gè)基本屬性:
- Capacity:容量,緩沖區(qū)中最多可以存儲(chǔ)的元素?cái)?shù)量;
- Position:當(dāng)前位置,下一個(gè)要被讀取或?qū)懭氲奈恢茫?/li>
- Limit:限制,緩沖區(qū)中的限制,表示可以讀寫(xiě)的元素?cái)?shù)量;
- Mark:標(biāo)記,可以讓緩沖區(qū)記住一個(gè)position或limit的值,通過(guò)調(diào)用reset()方法來(lái)恢復(fù)到這些值。
緩沖區(qū)的讀寫(xiě)操作都會(huì)修改position和limit屬性,例如在從緩沖區(qū)中讀取數(shù)據(jù)時(shí),position屬性會(huì)自動(dòng)向后移動(dòng),而limit屬性則不會(huì)更改,因此讀取操作只能讀取到limit位置之前的數(shù)據(jù)。
1.3 通道
通道(Channel)是Java NIO中網(wǎng)絡(luò)或文件IO操作的抽象,它類(lèi)似于傳統(tǒng)IO中的Stream,但是它更加靈活和高效。通道可以和緩沖區(qū)一起使用,讓數(shù)據(jù)直接在緩沖區(qū)之間進(jìn)行傳輸,可以使用Selector選擇器實(shí)現(xiàn)非阻塞IO操作。
通道主要分為以下四種類(lèi)型:
- FileChannel:用于文件讀寫(xiě)操作;
- DatagramChannel:用于UDP協(xié)議的網(wǎng)絡(luò)通信;
- SocketChannel:用于TCP協(xié)議的網(wǎng)絡(luò)通信;
- ServerSocketChannel:用于監(jiān)聽(tīng)TCP連接請(qǐng)求。
在使用NIO進(jìn)行網(wǎng)絡(luò)編程時(shí),我們常常使用SocketChannel和ServerSocketChannel來(lái)實(shí)現(xiàn)客戶(hù)端與服務(wù)器之間的通信。使用FileChannel可以完成對(duì)本地文件的讀寫(xiě)操作,使用DatagramChannel可以發(fā)送和接收UDP協(xié)議的數(shù)據(jù)包。
1.4 選擇器和選擇鍵
選擇器(Selector)和選擇鍵(SelectionKey)是Java NIO提供的另外兩個(gè)核心組件。選擇器用于檢測(cè)一個(gè)或多個(gè)通道的狀態(tài),并且可以根據(jù)通道狀態(tài)進(jìn)行非阻塞選擇操作。而選擇鍵則是一種將通道和選擇器進(jìn)行關(guān)聯(lián)的機(jī)制。
使用選擇器可以實(shí)現(xiàn)單線程管理多個(gè)通道的方式,以此實(shí)現(xiàn)高并發(fā)IO操作。在選擇器的模型中,每個(gè)通道都會(huì)注冊(cè)到一個(gè)選擇器上,并且每個(gè)通道都有一個(gè)其唯一的選擇鍵對(duì)象來(lái)代表這個(gè)通道。選擇鍵對(duì)象包含幾個(gè)標(biāo)志位,表示通道的當(dāng)前狀態(tài)等信息。
選擇器可以監(jiān)聽(tīng)多個(gè)通道的事件,例如連接就緒、讀取數(shù)據(jù)就緒、寫(xiě)入數(shù)據(jù)就緒等等。當(dāng)有一個(gè)或多個(gè)通道的事件就緒時(shí),選擇器就會(huì)自動(dòng)返回這些通道的選擇鍵,我們可以通過(guò)選擇鍵獲取到對(duì)應(yīng)的通道,然后進(jìn)行相應(yīng)的操作。
二、核心組件
Java NIO包含了四個(gè)核心組件:Channel、Buffer、Selector和SelectionKey。下面我們將分別介紹這四個(gè)組件的作用和使用方法。
2.1 Channel
Channel是Java NIO中網(wǎng)絡(luò)通信和文件IO操作的抽象,類(lèi)似于傳統(tǒng)IO中的Stream。它可以支持雙向讀寫(xiě)操作,并且可以通過(guò)緩沖區(qū)來(lái)直接進(jìn)行數(shù)據(jù)讀取或?qū)懭?。通常情況下,我們會(huì)創(chuàng)建一個(gè)Channel對(duì)象,然后將其綁定到一個(gè)Socket、File、Pipe等資源上進(jìn)行讀寫(xiě)操作。
NIO中主要提供了以下幾種類(lèi)型的Channel:
- FileChannel:用于文件讀寫(xiě)操作;
- DatagramChannel:用于UDP協(xié)議的網(wǎng)絡(luò)通信;
- SocketChannel:用于TCP協(xié)議的網(wǎng)絡(luò)通信;
- ServerSocketChannel:用于監(jiān)聽(tīng)TCP連接請(qǐng)求。
我們可以通過(guò)調(diào)用相應(yīng)的工廠方法來(lái)創(chuàng)建不同類(lèi)型的Channel。
2.1.1 FileChannel
FileChannel是Java NIO中對(duì)本地文件讀寫(xiě)操作的封裝。正如其名字所示,F(xiàn)ileChannel對(duì)象是針對(duì)文件的Channel,通過(guò)FileInputStream或FileOutputStream來(lái)獲取。通過(guò)FileChannel,我們可以實(shí)現(xiàn)對(duì)文件的讀取和寫(xiě)入操作,也可以使用它的position()方法來(lái)控制讀寫(xiě)位置,并配合Buffer進(jìn)行數(shù)據(jù)操作。
下面是一個(gè)使用FileChannel讀取文件的例子:
public static void main(String[] args) throws IOException { RandomAccessFile file = new RandomAccessFile("test.txt", "rw"); FileChannel channel = file.getChannel(); ByteBuffer buffer = ByteBuffer.allocate(1024); int bytesRead = channel.read(buffer); while (bytesRead != -1) { buffer.flip(); while (buffer.hasRemaining()) { System.out.print((char) buffer.get()); } buffer.clear(); bytesRead = channel.read(buffer); } file.close(); }
在這個(gè)例子中,我們使用FileChannel讀取一個(gè)名為test.txt的文本文件。首先,我們獲取到了一個(gè)文件對(duì)象,并通過(guò)它的getChannel()方法來(lái)獲取FileChannel對(duì)象;然后,我們創(chuàng)建一個(gè)容量為1024的ByteBuffer緩沖區(qū)來(lái)接收讀取到的數(shù)據(jù)。循環(huán)讀取數(shù)據(jù)時(shí),我們將緩沖區(qū)的limit和position屬性進(jìn)行調(diào)整,以便緩沖區(qū)可以正常存儲(chǔ)和處理讀取到的數(shù)據(jù)。
2.1.2 DatagramChannel
DatagramChannel是Java NIO中對(duì)UDP協(xié)議通信的封裝。通過(guò)DatagramChannel對(duì)象,我們可以實(shí)現(xiàn)發(fā)送和接收UDP數(shù)據(jù)包。它與TCP協(xié)議不同的是,UDP協(xié)議沒(méi)有連接的概念,所以無(wú)需像SocketChannel一樣先建立連接再開(kāi)始通信。
下面是一個(gè)使用DatagramChannel發(fā)送和接收UDP數(shù)據(jù)包的例子:
public static void main(String[] args) throws IOException { DatagramChannel channel = DatagramChannel.open(); channel.configureBlocking(false); ByteBuffer buffer = ByteBuffer.allocate(1024); Scanner scanner = new Scanner(System.in); while (scanner.hasNext()) { String message = scanner.next(); buffer.put(message.getBytes()); buffer.flip(); channel.send(buffer, new InetSocketAddress("127.0.0.1", 8888)); buffer.clear(); channel.receive(buffer); buffer.flip(); while (buffer.hasRemaining()) { System.out.print((char) buffer.get()); } buffer.clear(); } channel.close(); }
在這個(gè)例子中,我們創(chuàng)建了一個(gè)DatagramChannel對(duì)象,并調(diào)用configureBlocking(false)方法將其設(shè)置為非阻塞模式。然后,通過(guò)Scanner類(lèi)獲取用戶(hù)輸入的消息,將消息存放到ByteBuffer緩沖區(qū)中,并使用send()方法將其發(fā)送出去。接著,我們調(diào)用receive()方法來(lái)接收對(duì)方發(fā)送回來(lái)的消息,并將其打印到控制臺(tái)上。
2.1.3 SocketChannel
SocketChannel是Java NIO中對(duì)TCP協(xié)議通信的封裝。通過(guò)SocketChannel對(duì)象,我們可以實(shí)現(xiàn)對(duì)TCP連接的建立和通信交互。與傳統(tǒng)的Socket操作不同的是,SocketChannel基于非阻塞IO模式,可以在同一個(gè)線程內(nèi)同時(shí)管理多個(gè)通信連接,從而提高系統(tǒng)的并發(fā)處理能力。
下面是一個(gè)使用SocketChannel發(fā)送和接收TCP數(shù)據(jù)的例子:
public static void main(String[] args) throws IOException { SocketChannel channel = SocketChannel.open(); channel.configureBlocking(false); channel.connect(new InetSocketAddress("bing.com", 80)); while (!channel.finishConnect()) { // 等待連接建立完成 } ByteBuffer buffer = ByteBuffer.allocate(1024); String requestHeader = "GET / HTTP/1.1\r\n" + "Host: www.bing.com\r\n" + "Connection: Keep-Alive\r\n\r\n"; buffer.put(requestHeader.getBytes()); buffer.flip(); while (buffer.hasRemaining()) { channel.write(buffer); } buffer.clear(); while (channel.read(buffer) != -1) { buffer.flip(); while (buffer.hasRemaining()) { System.out.print((char) buffer.get()); } buffer.clear(); } channel.close(); }
在這個(gè)例子中,我們創(chuàng)建了一個(gè)SocketChannel對(duì)象,并調(diào)用configureBlocking(false)方法將其設(shè)置為非阻塞模式。然后,我們使用connect()方法來(lái)建立與目標(biāo)主機(jī)的TCP連接,并使用finishConnect()方法等待連接的建立完成。
接著,我們構(gòu)造了一個(gè)HTTP請(qǐng)求頭部,并將其存放到ByteBuffer緩沖區(qū)中,使用write()方法將其發(fā)送出去。最后,我們循環(huán)讀取SocketChannel中的數(shù)據(jù),將其打印到控制臺(tái)上。
2.1.4 ServerSocketChannel
ServerSocketChannel是Java NIO中用于監(jiān)聽(tīng)TCP連接請(qǐng)求的封裝。通過(guò)ServerSocketChannel,我們可以監(jiān)聽(tīng)來(lái)自客戶(hù)端的連接請(qǐng)求,并創(chuàng)建相應(yīng)的SocketChannel對(duì)象進(jìn)行通信交互。與傳統(tǒng)的ServerSocket不同的是,ServerSocketChannel基于非阻塞IO模式,可以在同一個(gè)線程內(nèi)同時(shí)管理多個(gè)客戶(hù)端連接請(qǐng)求,從而提高系統(tǒng)的并發(fā)處理能力。
下面是一個(gè)使用ServerSocketChannel監(jiān)聽(tīng)TCP連接請(qǐng)求的例子:
public static void main(String[] args) throws IOException { ServerSocketChannel serverChannel = ServerSocketChannel.open(); serverChannel.socket().bind(new InetSocketAddress(8888)); serverChannel.configureBlocking(false); while (true) { SocketChannel channel = serverChannel.accept(); if (channel != null) { ByteBuffer buffer = ByteBuffer.allocate(1024); int bytesRead = channel.read(buffer); while (bytesRead != -1) { buffer.flip(); while (buffer.hasRemaining()) { System.out.print((char) buffer.get()); } buffer.clear(); bytesRead = channel.read(buffer); } channel.close(); } } }
在這個(gè)例子中,我們創(chuàng)建了一個(gè)ServerSocketChannel對(duì)象,并通過(guò)bind()方法將其綁定到本地的8888端口上。然后,我們使用configureBlocking(false)方法將其設(shè)置為非阻塞模式,并啟動(dòng)一個(gè)無(wú)限循環(huán)來(lái)接收客戶(hù)端連接請(qǐng)求。
當(dāng)一個(gè)客戶(hù)端連接請(qǐng)求到達(dá)時(shí),我們調(diào)用accept()方法來(lái)接收它,并創(chuàng)建一個(gè)與客戶(hù)端通信的SocketChannel對(duì)象。接著,我們讀取SocketChannel中的數(shù)據(jù),并將其打印到控制臺(tái)上,完成一次客戶(hù)端請(qǐng)求的處理。
2.2 Buffer
Buffer是Java NIO中用于存儲(chǔ)IO操作數(shù)據(jù)的緩沖區(qū)組件,它提供了一種更加高效、可控的數(shù)據(jù)讀寫(xiě)方式。在進(jìn)行數(shù)據(jù)讀寫(xiě)操作時(shí),我們需要將數(shù)據(jù)存放到Buffer中,并且使用相應(yīng)的方法對(duì)其進(jìn)行操作。
NIO中主要提供了以下幾種類(lèi)型的Buffer:
- ByteBuffer:用于存儲(chǔ)字節(jié)數(shù)據(jù);
- CharBuffer:用于存儲(chǔ)字符數(shù)據(jù);
- ShortBuffer、IntBuffer、LongBuffer、FloatBuffer和DoubleBuffer:用于存儲(chǔ)各種基本類(lèi)型數(shù)據(jù)。
使用Buffer的方式具有一定的規(guī)律性,通常情況下,我們都需要遵循以下幾個(gè)步驟:
- 創(chuàng)建Buffer對(duì)象;
- 存儲(chǔ)數(shù)據(jù)到Buffer中;
- 調(diào)用flip()方法將Buffer從寫(xiě)模式切換為讀模式;
- 從Buffer中讀取數(shù)據(jù);
- 調(diào)用clear()或compact()方法清空或壓縮Buffer。
下面是一個(gè)使用ByteBuffer實(shí)現(xiàn)文件讀取功能的例子:
public static void main(String[] args) throws IOException { FileInputStream inputStream = new FileInputStream("test.txt"); FileChannel channel = inputStream.getChannel(); ByteBuffer buffer = ByteBuffer.allocate(1024); int bytesRead = channel.read(buffer); while (bytesRead != -1) { buffer.flip(); while (buffer.hasRemaining()) { System.out.print((char) buffer.get()); } buffer.clear(); bytesRead = channel.read(buffer); } inputStream.close(); }
在這個(gè)例子中,我們創(chuàng)建了一個(gè)ByteBuffer緩沖區(qū),并調(diào)用FileChannel的read()方法將文件中的數(shù)據(jù)讀取到緩沖區(qū)中。然后,我們調(diào)用flip()方法將緩沖區(qū)從寫(xiě)模式切換為讀模式,使用get()方法逐個(gè)獲取緩沖區(qū)中的字節(jié)數(shù)據(jù),并將其轉(zhuǎn)換成字符類(lèi)型輸出到控制臺(tái)上。
2.3 Selector
Selector是Java NIO中用于網(wǎng)絡(luò)通信的多路復(fù)用器組件,它可以監(jiān)控多個(gè)通道的IO操作狀態(tài),并在狀態(tài)就緒時(shí)將其返回給程序處理。使用Selector可以實(shí)現(xiàn)單線程管理多個(gè)通道的方式,以此實(shí)現(xiàn)高并發(fā)IO操作。
下面是一個(gè)使用Selector進(jìn)行TCP連接監(jiān)聽(tīng)的例子:
public static void main(String[] args) throws IOException { Selector selector = Selector.open(); ServerSocketChannel serverChannel = ServerSocketChannel.open(); serverChannel.socket().bind(new InetSocketAddress(8888)); serverChannel.configureBlocking(false); serverChannel.register(selector, SelectionKey.OP_ACCEPT); ByteBuffer buffer = ByteBuffer.allocate(1024); while (true) { int readyCount = selector.select(); if (readyCount == 0) { continue; } Set<SelectionKey> selectedKeys = selector.selectedKeys(); Iterator<SelectionKey> keyIterator = selectedKeys.iterator(); while (keyIterator.hasNext()) { SelectionKey key = keyIterator.next(); if (key.isAcceptable()) { SocketChannel channel = ((ServerSocketChannel) key.channel()).accept(); channel.configureBlocking(false); channel.register(selector, SelectionKey.OP_READ); } else if (key.isReadable()) { SocketChannel channel = (SocketChannel) key.channel(); int bytesRead = channel.read(buffer); while (bytesRead != -1) { buffer.flip(); while (buffer.hasRemaining()) { System.out.print((char) buffer.get()); } buffer.clear(); bytesRead = channel.read(buffer); } channel.close(); } keyIterator.remove(); } } }
在這個(gè)例子中,我們創(chuàng)建了一個(gè)Selector對(duì)象,并通過(guò)ServerSocketChannel的register()方法將其注冊(cè)到Selector上,監(jiān)聽(tīng)其連接請(qǐng)求的就緒狀態(tài)。當(dāng)有新的客戶(hù)端連接請(qǐng)求到達(dá)時(shí),我們調(diào)用accept()方法來(lái)接受它,并將其SocketChannel對(duì)象注冊(cè)到Selector上,監(jiān)聽(tīng)其讀取數(shù)據(jù)的就緒狀態(tài)。
在進(jìn)行網(wǎng)絡(luò)通信時(shí),如果有數(shù)據(jù)到達(dá),則Selector會(huì)檢測(cè)到其可讀性,然后調(diào)用對(duì)應(yīng)的處理方法進(jìn)行數(shù)據(jù)讀取和處理。最后,我們通過(guò)調(diào)用SelectionKey對(duì)象的remove()方法從Selector中移除監(jiān)聽(tīng)鍵,以便下次可以再詳細(xì)說(shuō)明一下上面的例子:
在while循環(huán)中,我們首先調(diào)用Selector的select()方法來(lái)檢測(cè)當(dāng)前是否有通道讀寫(xiě)事件就緒。如果沒(méi)有就緒的事件,則select()方法會(huì)阻塞,直到有事件發(fā)生為止。
如果有事件就緒,則調(diào)用selectedKeys()方法獲取所有就緒的SelectionKey對(duì)象,并通過(guò)迭代器依次處理每個(gè)事件。
如果當(dāng)前事件是連接請(qǐng)求事件,我們使用ServerSocketChannel的accept()方法接受該連接請(qǐng)求,并將其register()到Selector上進(jìn)行監(jiān)聽(tīng)讀取操作。
如果當(dāng)前事件是可讀事件,我們通過(guò)SelectionKey對(duì)象獲取對(duì)應(yīng)的SocketChannel,并從中讀取數(shù)據(jù),完成讀取后,關(guān)閉SocketChannel對(duì)象。
最后,我們通過(guò)調(diào)用SelectionKey對(duì)象的remove()方法從Selector中移除監(jiān)聽(tīng)鍵,以便下次可以重新注冊(cè)。
Selector不僅可以監(jiān)聽(tīng)TCP連接請(qǐng)求,還可以監(jiān)聽(tīng)其他網(wǎng)絡(luò)事件,如可讀、可寫(xiě)等。通過(guò)Selector的多路復(fù)用機(jī)制,我們可以在單線程內(nèi)同時(shí)管理多個(gè)通道的網(wǎng)絡(luò)IO事件,從而提高系統(tǒng)的并發(fā)處理能力。
三. 總結(jié)
Java NIO提供了一套靈活高效的IO操作API,可以幫助我們實(shí)現(xiàn)高并發(fā)、高性能的網(wǎng)絡(luò)通信功能。其中包括了Channel、Buffer和Selector三大核心組件,它們共同構(gòu)成了Java NIO的基礎(chǔ)框架。
相比傳統(tǒng)的IO操作方式,Java NIO具有更高的效率、更低的資源占用和更好的可擴(kuò)展性。因此,在開(kāi)發(fā)高并發(fā)、高性能網(wǎng)絡(luò)應(yīng)用時(shí),我們可以考慮使用Java NIO來(lái)實(shí)現(xiàn)。
以上就是Java NIO中四大核心組件的使用詳解的詳細(xì)內(nèi)容,更多關(guān)于Java NIO的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
SpringBoot發(fā)送短信驗(yàn)證碼的實(shí)例
第三方短信發(fā)送平臺(tái)有很多種,各個(gè)平臺(tái)有各自的優(yōu)缺點(diǎn),在選擇的時(shí)候可以根據(jù)自己的具體實(shí)際情況定奪,本文主要介紹了SpringBoot發(fā)送短信驗(yàn)證碼的實(shí)例,感興趣的可以了解一下2022-02-02RestTemplate發(fā)送HTTP?GET請(qǐng)求使用方法詳解
這篇文章主要為大家介紹了關(guān)于RestTemplate發(fā)送HTTP?GET請(qǐng)求的使用方法詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家<BR>33+多多進(jìn)步2022-03-03Java多線程CyclicBarrier的實(shí)現(xiàn)代碼
CyclicBarrier可以使一定數(shù)量的線程反復(fù)地在柵欄位置處匯集,本文通過(guò)實(shí)例代碼介紹下Java多線程CyclicBarrier的相關(guān)知識(shí),感興趣的朋友一起看看吧2022-02-02詳解阿里云maven鏡像庫(kù)配置(gradle,maven)
這篇文章主要介紹了詳解阿里云maven鏡像庫(kù)配置(gradle,maven),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-02-02springboot指定profiles啟動(dòng)失敗問(wèn)題及解決
這篇文章主要介紹了springboot指定profiles啟動(dòng)失敗問(wèn)題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-04-04springboot遠(yuǎn)程debug調(diào)試全過(guò)程
這篇文章主要介紹了springboot遠(yuǎn)程debug調(diào)試全過(guò)程,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-05-05