日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程语言 > java >内容正文

java

Java NIO使用及原理分析

發(fā)布時(shí)間:2024/4/17 java 35 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Java NIO使用及原理分析 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

http://blog.csdn.net/wuxianglong/article/details/6604817

轉(zhuǎn)載自:李會(huì)軍?寧?kù)o致遠(yuǎn)

最近由于工作關(guān)系要做一些Java方面的開發(fā),其中最重要的一塊就是Java NIO(New I/O),盡管很早以前了解過(guò)一些,但并沒有認(rèn)真去看過(guò)它的實(shí)現(xiàn)原理,也沒有機(jī)會(huì)在工作中使用,這次也好重新研究一下,順便寫點(diǎn)東西,就當(dāng)是自己學(xué)習(xí) Java NIO的筆記了。本文為NIO使用及原理分析的第一篇,將會(huì)介紹NIO中幾個(gè)重要的概念。

在Java1.4之前的I/O系統(tǒng)中,提供的都是面向流的I/O系統(tǒng),系統(tǒng)一次一個(gè)字節(jié)地處理數(shù)據(jù),一個(gè)輸入流產(chǎn)生一個(gè)字節(jié)的數(shù)據(jù),一個(gè)輸出流消費(fèi)一個(gè)字節(jié)的數(shù)據(jù),面向流的I/O速度非常慢,而在Java 1.4中推出了NIO,這是一個(gè)面向塊的I/O系統(tǒng),系統(tǒng)以塊的方式處理處理,每一個(gè)操作在一步中產(chǎn)生或者消費(fèi)一個(gè)數(shù)據(jù)庫(kù),按塊處理要比按字節(jié)處理數(shù)據(jù)快的多。

在NIO中有幾個(gè)核心對(duì)象需要掌握:緩沖區(qū)(Buffer)、通道(Channel)、選擇器(Selector)。

緩沖區(qū)Buffer

緩沖區(qū)實(shí)際上是一個(gè)容器對(duì)象,更直接的說(shuō),其實(shí)就是一個(gè)數(shù)組,在NIO庫(kù)中,所有數(shù)據(jù)都是用緩沖區(qū)處理的。在讀取數(shù)據(jù)時(shí),它是直接讀到緩沖區(qū)中的; 在寫入數(shù)據(jù)時(shí),它也是寫入到緩沖區(qū)中的;任何時(shí)候訪問(wèn) NIO 中的數(shù)據(jù),都是將它放到緩沖區(qū)中。而在面向流I/O系統(tǒng)中,所有數(shù)據(jù)都是直接寫入或者直接將數(shù)據(jù)讀取到Stream對(duì)象中。

在NIO中,所有的緩沖區(qū)類型都繼承于抽象類Buffer,最常用的就是ByteBuffer,對(duì)于Java中的基本類型,基本都有一個(gè)具體Buffer類型與之相對(duì)應(yīng),它們之間的繼承關(guān)系如下圖所示:

下面是一個(gè)簡(jiǎn)單的使用IntBuffer的例子:

[java] view plaincopyprint?

?

import java.nio.IntBuffer;public class TestIntBuffer {public static void main(String[] args) {// 分配新的int緩沖區(qū),參數(shù)為緩沖區(qū)容量// 新緩沖區(qū)的當(dāng)前位置將為零,其界限(限制位置)將為其容量。它將具有一個(gè)底層實(shí)現(xiàn)數(shù)組,其數(shù)組偏移量將為零。IntBuffer buffer = IntBuffer.allocate(8);for (int i = 0; i < buffer.capacity(); ++i) {int j = 2 * (i + 1);// 將給定整數(shù)寫入此緩沖區(qū)的當(dāng)前位置,當(dāng)前位置遞增buffer.put(j);}// 重設(shè)此緩沖區(qū),將限制設(shè)置為當(dāng)前位置,然后將當(dāng)前位置設(shè)置為0buffer.flip();// 查看在當(dāng)前位置和限制位置之間是否有元素while (buffer.hasRemaining()) {// 讀取此緩沖區(qū)當(dāng)前位置的整數(shù),然后當(dāng)前位置遞增int j = buffer.get();System.out.print(j + " ");}}}

運(yùn)行后可以看到:

在后面我們還會(huì)繼續(xù)分析Buffer對(duì)象,以及它的幾個(gè)重要的屬性。

通道Channel

通道是一個(gè)對(duì)象,通過(guò)它可以讀取和寫入數(shù)據(jù),當(dāng)然了所有數(shù)據(jù)都通過(guò)Buffer對(duì)象來(lái)處理。我們永遠(yuǎn)不會(huì)將字節(jié)直接寫入通道中,相反是將數(shù)據(jù)寫入包含一個(gè)或者多個(gè)字節(jié)的緩沖區(qū)。同樣不會(huì)直接從通道中讀取字節(jié),而是將數(shù)據(jù)從通道讀入緩沖區(qū),再?gòu)木彌_區(qū)獲取這個(gè)字節(jié)。

在NIO中,提供了多種通道對(duì)象,而所有的通道對(duì)象都實(shí)現(xiàn)了Channel接口。它們之間的繼承關(guān)系如下圖所示:

使用NIO讀取數(shù)據(jù)

在前面我們說(shuō)過(guò),任何時(shí)候讀取數(shù)據(jù),都不是直接從通道讀取,而是從通道讀取到緩沖區(qū)。所以使用NIO讀取數(shù)據(jù)可以分為下面三個(gè)步驟:
1. 從FileInputStream獲取Channel
2. 創(chuàng)建Buffer
3. 將數(shù)據(jù)從Channel讀取到Buffer中

下面是一個(gè)簡(jiǎn)單的使用NIO從文件中讀取數(shù)據(jù)的例子:

[java] view plaincopyprint?

?

import java.io.*; import java.nio.*; import java.nio.channels.*;public class Program {static public void main( String args[] ) throws Exception {FileInputStream fin = new FileInputStream("c:\\test.txt");// 獲取通道FileChannel fc = fin.getChannel();// 創(chuàng)建緩沖區(qū)ByteBuffer buffer = ByteBuffer.allocate(1024);// 讀取數(shù)據(jù)到緩沖區(qū)fc.read(buffer);buffer.flip();while (buffer.remaining()>0) {byte b = buffer.get();System.out.print(((char)b));}fin.close();} }

使用NIO寫入數(shù)據(jù)

使用NIO寫入數(shù)據(jù)與讀取數(shù)據(jù)的過(guò)程類似,同樣數(shù)據(jù)不是直接寫入通道,而是寫入緩沖區(qū),可以分為下面三個(gè)步驟:
1. 從FileInputStream獲取Channel
2. 創(chuàng)建Buffer
3. 將數(shù)據(jù)從Channel寫入到Buffer中

下面是一個(gè)簡(jiǎn)單的使用NIO向文件中寫入數(shù)據(jù)的例子:

[java] view plaincopyprint?

?

import java.io.*; import java.nio.*; import java.nio.channels.*;public class Program {static private final byte message[] = { 83, 111, 109, 101, 32,98, 121, 116, 101, 115, 46 };static public void main( String args[] ) throws Exception {FileOutputStream fout = new FileOutputStream( "c:\\test.txt" );FileChannel fc = fout.getChannel();ByteBuffer buffer = ByteBuffer.allocate( 1024 );for (int i=0; i<message.length; ++i) {buffer.put( message[i] );}buffer.flip();fc.write( buffer );fout.close();} }

本文介紹了Java NIO中三個(gè)核心概念中的兩個(gè),并且看了兩個(gè)簡(jiǎn)單的示例,分別是使用NIO進(jìn)行數(shù)據(jù)的讀取和寫入,Java NIO中最重要的一塊Nonblocking I/O將在第三篇中進(jìn)行分析,下篇將會(huì)介紹Buffer內(nèi)部實(shí)現(xiàn)。

?

?

在第一篇中,我們介紹了NIO中的兩個(gè)核心對(duì)象:緩沖區(qū)和通道,在談到緩沖區(qū)時(shí),我們說(shuō)緩沖區(qū)對(duì)象本質(zhì)上是一個(gè)數(shù)組,但它其實(shí)是一個(gè)特殊的數(shù)組,緩沖區(qū)對(duì)象內(nèi)置了一些機(jī)制,能夠跟蹤和記錄緩沖區(qū)的狀態(tài)變化情況,如果我們使用get()方法從緩沖區(qū)獲取數(shù)據(jù)或者使用put()方法把數(shù)據(jù)寫入緩沖區(qū),都會(huì)引起緩沖區(qū)狀態(tài)的變化。本文為NIO使用及原理分析的第二篇,將會(huì)分析NIO中的Buffer對(duì)象。

在緩沖區(qū)中,最重要的屬性有下面三個(gè),它們一起合作完成對(duì)緩沖區(qū)內(nèi)部狀態(tài)的變化跟蹤:

position:指定了下一個(gè)將要被寫入或者讀取的元素索引,它的值由get()/put()方法自動(dòng)更新,在新創(chuàng)建一個(gè)Buffer對(duì)象時(shí),position被初始化為0。

limit:指定還有多少數(shù)據(jù)需要取出(在從緩沖區(qū)寫入通道時(shí)),或者還有多少空間可以放入數(shù)據(jù)(在從通道讀入緩沖區(qū)時(shí))。

capacity:指定了可以存儲(chǔ)在緩沖區(qū)中的最大數(shù)據(jù)容量,實(shí)際上,它指定了底層數(shù)組的大小,或者至少是指定了準(zhǔn)許我們使用的底層數(shù)組的容量。

以上四個(gè)屬性值之間有一些相對(duì)大小的關(guān)系:0 <= position <= limit <= capacity。如果我們創(chuàng)建一個(gè)新的容量大小為10的ByteBuffer對(duì)象,在初始化的時(shí)候,position設(shè)置為0,limit和 capacity被設(shè)置為10,在以后使用ByteBuffer對(duì)象過(guò)程中,capacity的值不會(huì)再發(fā)生變化,而其它兩個(gè)個(gè)將會(huì)隨著使用而變化。四個(gè)屬性值分別如圖所示:

現(xiàn)在我們可以從通道中讀取一些數(shù)據(jù)到緩沖區(qū)中,注意從通道讀取數(shù)據(jù),相當(dāng)于往緩沖區(qū)中寫入數(shù)據(jù)。如果讀取4個(gè)自己的數(shù)據(jù),則此時(shí)position的值為4,即下一個(gè)將要被寫入的字節(jié)索引為4,而limit仍然是10,如下圖所示:

下一步把讀取的數(shù)據(jù)寫入到輸出通道中,相當(dāng)于從緩沖區(qū)中讀取數(shù)據(jù),在此之前,必須調(diào)用flip()方法,該方法將會(huì)完成兩件事情:

1. 把limit設(shè)置為當(dāng)前的position值
2. 把position設(shè)置為0

由于position被設(shè)置為0,所以可以保證在下一步輸出時(shí)讀取到的是緩沖區(qū)中的第一個(gè)字節(jié),而limit被設(shè)置為當(dāng)前的position,可以保證讀取的數(shù)據(jù)正好是之前寫入到緩沖區(qū)中的數(shù)據(jù),如下圖所示:

現(xiàn)在調(diào)用get()方法從緩沖區(qū)中讀取數(shù)據(jù)寫入到輸出通道,這會(huì)導(dǎo)致position的增加而limit保持不變,但position不會(huì)超過(guò)limit的值,所以在讀取我們之前寫入到緩沖區(qū)中的4個(gè)自己之后,position和limit的值都為4,如下圖所示:

在從緩沖區(qū)中讀取數(shù)據(jù)完畢后,limit的值仍然保持在我們調(diào)用flip()方法時(shí)的值,調(diào)用clear()方法能夠把所有的狀態(tài)變化設(shè)置為初始化時(shí)的值,如下圖所示:

最后我們用一段代碼來(lái)驗(yàn)證這個(gè)過(guò)程,如下所示:

[java] view plaincopyprint?

?

import java.io.*; import java.nio.*; import java.nio.channels.*;public class Program {public static void main(String args[]) throws Exception {FileInputStream fin = new FileInputStream("d:\\test.txt");FileChannel fc = fin.getChannel();ByteBuffer buffer = ByteBuffer.allocate(10);output("初始化", buffer);fc.read(buffer);output("調(diào)用read()", buffer);buffer.flip();output("調(diào)用flip()", buffer);while (buffer.remaining() > 0) {byte b = buffer.get();// System.out.print(((char)b));}output("調(diào)用get()", buffer);buffer.clear();output("調(diào)用clear()", buffer);fin.close();}public static void output(String step, Buffer buffer) {System.out.println(step + " : ");System.out.print("capacity: " + buffer.capacity() + ", ");System.out.print("position: " + buffer.position() + ", ");System.out.println("limit: " + buffer.limit());System.out.println();} }

完成的輸出結(jié)果為:

這與我們上面演示的過(guò)程一致。在后面的文章中,我們繼續(xù)介紹NIO中關(guān)于緩沖區(qū)一些更高級(jí)的使用。

?

?

在上一篇文章中介紹了緩沖區(qū)內(nèi)部對(duì)于狀態(tài)變化的跟蹤機(jī)制,而對(duì)于NIO中緩沖區(qū)來(lái)說(shuō),還有很多的內(nèi)容值的學(xué)習(xí),如緩沖區(qū)的分片與數(shù)據(jù)共享,只讀緩沖區(qū)等。在本文中我們來(lái)看一下緩沖區(qū)一些更細(xì)節(jié)的內(nèi)容。

緩沖區(qū)的分配

在前面的幾個(gè)例子中,我們已經(jīng)看過(guò)了,在創(chuàng)建一個(gè)緩沖區(qū)對(duì)象時(shí),會(huì)調(diào)用靜態(tài)方法allocate()來(lái)指定緩沖區(qū)的容量,其實(shí)調(diào)用 allocate()相當(dāng)于創(chuàng)建了一個(gè)指定大小的數(shù)組,并把它包裝為緩沖區(qū)對(duì)象。或者我們也可以直接將一個(gè)現(xiàn)有的數(shù)組,包裝為緩沖區(qū)對(duì)象,如下示例代碼所示:

[java] view plaincopyprint?

?

public class BufferWrap {public void myMethod(){// 分配指定大小的緩沖區(qū)ByteBuffer buffer1 = ByteBuffer.allocate(10);// 包裝一個(gè)現(xiàn)有的數(shù)組byte array[] = new byte[10];ByteBuffer buffer2 = ByteBuffer.wrap( array );} }

緩沖區(qū)分片

在NIO中,除了可以分配或者包裝一個(gè)緩沖區(qū)對(duì)象外,還可以根據(jù)現(xiàn)有的緩沖區(qū)對(duì)象來(lái)創(chuàng)建一個(gè)子緩沖區(qū),即在現(xiàn)有緩沖區(qū)上切出一片來(lái)作為一個(gè)新的緩沖區(qū),但現(xiàn)有的緩沖區(qū)與創(chuàng)建的子緩沖區(qū)在底層數(shù)組層面上是數(shù)據(jù)共享的,也就是說(shuō),子緩沖區(qū)相當(dāng)于是現(xiàn)有緩沖區(qū)的一個(gè)視圖窗口。調(diào)用slice()方法可以創(chuàng)建一個(gè)子緩沖區(qū),讓我們通過(guò)例子來(lái)看一下:

[java] view plaincopyprint? import java.nio.*;public class Program {static public void main( String args[] ) throws Exception {ByteBuffer buffer = ByteBuffer.allocate( 10 );// 緩沖區(qū)中的數(shù)據(jù)0-9for (int i=0; i<buffer.capacity(); ++i) {buffer.put( (byte)i );}// 創(chuàng)建子緩沖區(qū)buffer.position( 3 );buffer.limit( 7 );ByteBuffer slice = buffer.slice();// 改變子緩沖區(qū)的內(nèi)容for (int i=0; i<slice.capacity(); ++i) {byte b = slice.get( i );b *= 10;slice.put( i, b );}buffer.position( 0 );buffer.limit( buffer.capacity() );while (buffer.remaining()>0) {System.out.println( buffer.get() );}} }

在該示例中,分配了一個(gè)容量大小為10的緩沖區(qū),并在其中放入了數(shù)據(jù)0-9,而在該緩沖區(qū)基礎(chǔ)之上又創(chuàng)建了一個(gè)子緩沖區(qū),并改變子緩沖區(qū)中的內(nèi)容,從最后輸出的結(jié)果來(lái)看,只有子緩沖區(qū)“可見的”那部分?jǐn)?shù)據(jù)發(fā)生了變化,并且說(shuō)明子緩沖區(qū)與原緩沖區(qū)是數(shù)據(jù)共享的,輸出結(jié)果如下所示:

只讀緩沖區(qū)

只讀緩沖區(qū)非常簡(jiǎn)單,可以讀取它們,但是不能向它們寫入數(shù)據(jù)。可以通過(guò)調(diào)用緩沖區(qū)的asReadOnlyBuffer()方法,將任何常規(guī)緩沖區(qū)轉(zhuǎn) 換為只讀緩沖區(qū),這個(gè)方法返回一個(gè)與原緩沖區(qū)完全相同的緩沖區(qū),并與原緩沖區(qū)共享數(shù)據(jù),只不過(guò)它是只讀的。如果原緩沖區(qū)的內(nèi)容發(fā)生了變化,只讀緩沖區(qū)的內(nèi)容也隨之發(fā)生變化:

[java] view plaincopyprint? import java.nio.*;public class Program {static public void main( String args[] ) throws Exception {ByteBuffer buffer = ByteBuffer.allocate( 10 );// 緩沖區(qū)中的數(shù)據(jù)0-9for (int i=0; i<buffer.capacity(); ++i) {buffer.put( (byte)i );}// 創(chuàng)建只讀緩沖區(qū)ByteBuffer readonly = buffer.asReadOnlyBuffer();// 改變?cè)彌_區(qū)的內(nèi)容for (int i=0; i<buffer.capacity(); ++i) {byte b = buffer.get( i );b *= 10;buffer.put( i, b );}readonly.position(0);readonly.limit(buffer.capacity());// 只讀緩沖區(qū)的內(nèi)容也隨之改變while (readonly.remaining()>0) {System.out.println( readonly.get());}} }

如果嘗試修改只讀緩沖區(qū)的內(nèi)容,則會(huì)報(bào)ReadOnlyBufferException異常。只讀緩沖區(qū)對(duì)于保護(hù)數(shù)據(jù)很有用。在將緩沖區(qū)傳遞給某個(gè) 對(duì)象的方法時(shí),無(wú)法知道這個(gè)方法是否會(huì)修改緩沖區(qū)中的數(shù)據(jù)。創(chuàng)建一個(gè)只讀的緩沖區(qū)可以保證該緩沖區(qū)不會(huì)被修改。只可以把常規(guī)緩沖區(qū)轉(zhuǎn)換為只讀緩沖區(qū),而不能將只讀的緩沖區(qū)轉(zhuǎn)換為可寫的緩沖區(qū)。

直接緩沖區(qū)

直接緩沖區(qū)是為加快I/O速度,使用一種特殊方式為其分配內(nèi)存的緩沖區(qū),JDK文檔中的描述為:給定一個(gè)直接字節(jié)緩沖區(qū),Java虛擬機(jī)將盡最大努 力直接對(duì)它執(zhí)行本機(jī)I/O操作。也就是說(shuō),它會(huì)在每一次調(diào)用底層操作系統(tǒng)的本機(jī)I/O操作之前(或之后),嘗試避免將緩沖區(qū)的內(nèi)容拷貝到一個(gè)中間緩沖區(qū)中 或者從一個(gè)中間緩沖區(qū)中拷貝數(shù)據(jù)。要分配直接緩沖區(qū),需要調(diào)用allocateDirect()方法,而不是allocate()方法,使用方式與普通緩沖區(qū)并無(wú)區(qū)別,如下面的拷貝文件示例:

[java] view plaincopyprint? import java.io.*; import java.nio.*; import java.nio.channels.*;public class Program {static public void main( String args[] ) throws Exception {String infile = "c:\\test.txt";FileInputStream fin = new FileInputStream( infile );FileChannel fcin = fin.getChannel();String outfile = String.format("c:\\testcopy.txt");FileOutputStream fout = new FileOutputStream( outfile ); FileChannel fcout = fout.getChannel();// 使用allocateDirect,而不是allocateByteBuffer buffer = ByteBuffer.allocateDirect( 1024 );while (true) {buffer.clear();int r = fcin.read( buffer );if (r==-1) {break;}buffer.flip();fcout.write( buffer );}} }

內(nèi)存映射文件I/O

內(nèi)存映射文件I/O是一種讀和寫文件數(shù)據(jù)的方法,它可以比常規(guī)的基于流或者基于通道的I/O快的多。內(nèi)存映射文件I/O是通過(guò)使文件中的數(shù)據(jù)出現(xiàn)為 內(nèi)存數(shù)組的內(nèi)容來(lái)完成的,這其初聽起來(lái)似乎不過(guò)就是將整個(gè)文件讀到內(nèi)存中,但是事實(shí)上并不是這樣。一般來(lái)說(shuō),只有文件中實(shí)際讀取或者寫入的部分才會(huì)映射到內(nèi)存中。如下面的示例代碼:

[java] view plaincopyprint?

?

import java.io.*; import java.nio.*; import java.nio.channels.*;public class Program {static private final int start = 0;static private final int size = 1024;static public void main( String args[] ) throws Exception {RandomAccessFile raf = new RandomAccessFile( "c:\\test.txt", "rw" );FileChannel fc = raf.getChannel();MappedByteBuffer mbb = fc.map( FileChannel.MapMode.READ_WRITE,start, size );mbb.put( 0, (byte)97 );mbb.put( 1023, (byte)122 );raf.close();} }

關(guān)于緩沖區(qū)的細(xì)節(jié)內(nèi)容,我們已經(jīng)用了兩篇文章來(lái)介紹。在下一篇中將會(huì)介紹NIO中更有趣的部分Nonblocking I/O。

?

在上一篇文章中介紹了關(guān)于緩沖區(qū)的一些細(xì)節(jié)內(nèi)容,現(xiàn)在終于可以進(jìn)入NIO中最有意思的部分非阻塞I/O。通常在進(jìn)行同步I/O操作時(shí),如果讀取數(shù)據(jù),代碼會(huì)阻塞直至有 可供讀取的數(shù)據(jù)。同樣,寫入調(diào)用將會(huì)阻塞直至數(shù)據(jù)能夠?qū)懭搿鹘y(tǒng)的Server/Client模式會(huì)基于TPR(Thread per Request),服務(wù)器會(huì)為每個(gè)客戶端請(qǐng)求建立一個(gè)線程,由該線程單獨(dú)負(fù)責(zé)處理一個(gè)客戶請(qǐng)求。這種模式帶來(lái)的一個(gè)問(wèn)題就是線程數(shù)量的劇增,大量的線程會(huì)增大服務(wù)器的開銷。大多數(shù)的實(shí)現(xiàn)為了避免這個(gè)問(wèn)題,都采用了線程池模型,并設(shè)置線程池線程的最大數(shù)量,這由帶來(lái)了新的問(wèn)題,如果線程池中有200個(gè)線程,而有200個(gè)用戶都在進(jìn)行大文件下載,會(huì)導(dǎo)致第201個(gè)用戶的請(qǐng)求無(wú)法及時(shí)處理,即便第201個(gè)用戶只想請(qǐng)求一個(gè)幾KB大小的頁(yè)面。傳統(tǒng)的 Server/Client模式如下圖所示:

NIO中非阻塞I/O采用了基于Reactor模式的工作方式,I/O調(diào)用不會(huì)被阻塞,相反是注冊(cè)感興趣的特定I/O事件,如可讀數(shù)據(jù)到達(dá),新的套接字連接等等,在發(fā)生特定事件時(shí),系統(tǒng)再通知我們。NIO中實(shí)現(xiàn)非阻塞I/O的核心對(duì)象就是Selector,Selector就是注冊(cè)各種I/O事件地 方,而且當(dāng)那些事件發(fā)生時(shí),就是這個(gè)對(duì)象告訴我們所發(fā)生的事件,如下圖所示:

從圖中可以看出,當(dāng)有讀或?qū)懙热魏巫?cè)的事件發(fā)生時(shí),可以從Selector中獲得相應(yīng)的SelectionKey,同時(shí)從 SelectionKey中可以找到發(fā)生的事件和該事件所發(fā)生的具體的SelectableChannel,以獲得客戶端發(fā)送過(guò)來(lái)的數(shù)據(jù)。關(guān)于 SelectableChannel的可以參考Java NIO使用及原理分析(一)

使用NIO中非阻塞I/O編寫服務(wù)器處理程序,大體上可以分為下面三個(gè)步驟:

1. 向Selector對(duì)象注冊(cè)感興趣的事件
2. 從Selector中獲取感興趣的事件
3. 根據(jù)不同的事件進(jìn)行相應(yīng)的處理

接下來(lái)我們用一個(gè)簡(jiǎn)單的示例來(lái)說(shuō)明整個(gè)過(guò)程。首先是向Selector對(duì)象注冊(cè)感興趣的事件:

[java] view plaincopyprint?

?

/** 注冊(cè)事件* */ protected Selector getSelector() throws IOException {// 創(chuàng)建Selector對(duì)象Selector sel = Selector.open();// 創(chuàng)建可選擇通道,并配置為非阻塞模式ServerSocketChannel server = ServerSocketChannel.open();server.configureBlocking(false);// 綁定通道到指定端口ServerSocket socket = server.socket();InetSocketAddress address = new InetSocketAddress(port);socket.bind(address);// 向Selector中注冊(cè)感興趣的事件server.register(sel, SelectionKey.OP_ACCEPT); return sel; }

創(chuàng)建了ServerSocketChannel對(duì)象,并調(diào)用configureBlocking()方法,配置為非阻塞模式,接下來(lái)的三行代碼把該通道綁定到指定端口,最后向Selector中注冊(cè)事件,此處指定的是參數(shù)是OP_ACCEPT,即指定我們想要監(jiān)聽accept事件,也就是新的連接發(fā) 生時(shí)所產(chǎn)生的事件,對(duì)于ServerSocketChannel通道來(lái)說(shuō),我們唯一可以指定的參數(shù)就是OP_ACCEPT。

從Selector中獲取感興趣的事件,即開始監(jiān)聽,進(jìn)入內(nèi)部循環(huán):

[java] view plaincopyprint? * 開始監(jiān)聽* */ public void listen() { System.out.println("listen on " + port);try { while(true) { // 該調(diào)用會(huì)阻塞,直到至少有一個(gè)事件發(fā)生selector.select(); Set<SelectionKey> keys = selector.selectedKeys();Iterator<SelectionKey> iter = keys.iterator();while (iter.hasNext()) { SelectionKey key = (SelectionKey) iter.next(); iter.remove(); process(key); } } } catch (IOException e) { e.printStackTrace();} }

在非阻塞I/O中,內(nèi)部循環(huán)模式基本都是遵循這種方式。首先調(diào)用select()方法,該方法會(huì)阻塞,直到至少有一個(gè)事件發(fā)生,然后再使用selectedKeys()方法獲取發(fā)生事件的SelectionKey,再使用迭代器進(jìn)行循環(huán)。

最后一步就是根據(jù)不同的事件,編寫相應(yīng)的處理代碼:

[java] view plaincopyprint?

?

/** 根據(jù)不同的事件做處理* */ protected void process(SelectionKey key) throws IOException{// 接收請(qǐng)求if (key.isAcceptable()) {ServerSocketChannel server = (ServerSocketChannel) key.channel();SocketChannel channel = server.accept();channel.configureBlocking(false);channel.register(selector, SelectionKey.OP_READ);}// 讀信息else if (key.isReadable()) {SocketChannel channel = (SocketChannel) key.channel(); int count = channel.read(buffer); if (count > 0) { buffer.flip(); CharBuffer charBuffer = decoder.decode(buffer); name = charBuffer.toString(); SelectionKey sKey = channel.register(selector, SelectionKey.OP_WRITE); sKey.attach(name); } else { channel.close(); } buffer.clear(); }// 寫事件else if (key.isWritable()) {SocketChannel channel = (SocketChannel) key.channel(); String name = (String) key.attachment(); ByteBuffer block = encoder.encode(CharBuffer.wrap("Hello " + name)); if(block != null){channel.write(block);}else{channel.close();}} }

此處分別判斷是接受請(qǐng)求、讀數(shù)據(jù)還是寫事件,分別作不同的處理。

到這里關(guān)于Java NIO使用及原理分析的四篇文章就全部完成了。Java NIO提供了通道、緩沖區(qū)、選擇器這樣一組抽象概念,極大的簡(jiǎn)化了我們編寫高性能并發(fā)型服務(wù)器程序,后面有機(jī)會(huì)我會(huì)繼續(xù)談?wù)勈褂肑ava NIO的一些想法。

?

?

總結(jié)

以上是生活随笔為你收集整理的Java NIO使用及原理分析的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。