Java NIO

(轉(zhuǎn)載說明:本文非原創(chuàng),轉(zhuǎn)載自http://ifeve.com/java-nio-all/)

Java NIO: Channels and Buffers(通道和緩沖區(qū))
標(biāo)準(zhǔn)的IO基于字節(jié)流和字符流進(jìn)行操作的,而NIO是基于通道(Channel)和緩沖區(qū)(Buffer)進(jìn)行操作,數(shù)據(jù)總是從通道讀取到緩沖區(qū)中,或者從緩沖區(qū)寫入到通道中。

Java NIO: Non-blocking IO(非阻塞IO)
Java NIO可以讓你非阻塞的使用IO,例如:當(dāng)線程從通道讀取數(shù)據(jù)到緩沖區(qū)時(shí),線程還是可以進(jìn)行其他事情。當(dāng)數(shù)據(jù)被寫入到緩沖區(qū)時(shí),線程可以繼續(xù)處理它。從緩沖區(qū)寫入通道也類似。

Java NIO: Selectors(選擇器)
Java NIO引入了選擇器的概念,選擇器用于監(jiān)聽多個(gè)通道的事件(比如:連接打開,數(shù)據(jù)到達(dá))。因此,單個(gè)的線程可以監(jiān)聽多個(gè)數(shù)據(jù)通道。

1 Channel

通道的讀寫是雙向的,可異步的,并且都需要通過Buffer來進(jìn)行。這些是Java NIO中最重要的通道的實(shí)現(xiàn):
FileChannel
DatagramChannel
SocketChannel
ServerSocketChannel

FileChannel 從文件中讀寫數(shù)據(jù)。DatagramChannel 能通過UDP讀寫網(wǎng)絡(luò)中的數(shù)據(jù)。SocketChannel 能通過TCP讀寫網(wǎng)絡(luò)中的數(shù)據(jù)。ServerSocketChannel可以監(jiān)聽新進(jìn)來的TCP連接,像Web服務(wù)器那樣。對(duì)每一個(gè)新進(jìn)來的連接都會(huì)創(chuàng)建一個(gè)SocketChannel。

2 Buffer

首先看個(gè)例子

RandomAccessFile file = new RandomAccessFile("a.txt","rw");
FileChannel channel = file.getChannel();
ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = channel.read(buf);
while(bytesRead!=0){ buf.flip(); while(buf.hasRemaining())
  { 
            System.out.println((char)buf.get()); 
  }
   buf.clear(); 
   bytesRead = channel.read(buf);
}

為了理解Buffer的工作原理,需要熟悉它的三個(gè)屬性:
capacity
作為一個(gè)內(nèi)存塊,Buffer有一個(gè)固定的大小值,也叫“capacity”.你只能往里寫capacity個(gè)byte、long,char等類型。一旦Buffer滿了,需要將其清空(通過讀數(shù)據(jù)或者清除數(shù)據(jù))才能繼續(xù)寫數(shù)據(jù)往里寫數(shù)據(jù)。

position
當(dāng)你寫數(shù)據(jù)到Buffer中時(shí),position表示當(dāng)前的位置。初始的position值為0.當(dāng)一個(gè)byte、long等數(shù)據(jù)寫到Buffer后, position會(huì)向前移動(dòng)到下一個(gè)可插入數(shù)據(jù)的Buffer單元。position最大可為capacity – 1.當(dāng)讀取數(shù)據(jù)時(shí),也是從某個(gè)特定位置讀。當(dāng)將Buffer從寫模式切換到讀模式,position會(huì)被重置為0. 當(dāng)從Buffer的position處讀取數(shù)據(jù)時(shí),position向前移動(dòng)到下一個(gè)可讀的位置。

limit
在寫模式下,Buffer的limit表示你最多能往Buffer里寫多少數(shù)據(jù)。 寫模式下,limit等于Buffer的capacity。當(dāng)切換Buffer到讀模式時(shí), limit表示你最多能讀到多少數(shù)據(jù)。因此,當(dāng)切換Buffer到讀模式時(shí),limit會(huì)被設(shè)置成寫模式下的position值。換句話說,你能讀到之前寫入的所有數(shù)據(jù)(limit被設(shè)置成已寫數(shù)據(jù)的數(shù)量,這個(gè)值在寫模式下就是position)

2.1 Buffer的類型

Java NIO 有以下Buffer類型
ByteBuffer
MappedByteBuffer
CharBuffer
DoubleBuffer
FloatBuffer
IntBuffer
LongBuffer
ShortBuffer
如你所見,這些Buffer類型代表了不同的數(shù)據(jù)類型。換句話說,就是可以通過char,short,int,long,float 或 double類型來操作緩沖區(qū)中的字節(jié)。

2.2 Buffer的分配

要想獲得一個(gè)Buffer對(duì)象首先要進(jìn)行分配。 每一個(gè)Buffer類都有一個(gè)allocate方法。下面是一個(gè)分配48字節(jié)capacity的ByteBuffer的例子。

ByteBuffer buf = ByteBuffer.allocate(48);
CharBuffer buf = CharBuffer.allocate(1024);
2.3向Buffer中寫數(shù)據(jù)

寫數(shù)據(jù)到Buffer有兩種方式:
從Channel寫到Buffer。
通過Buffer的put()方法寫到Buffer里。

從Channel寫到Buffer的例子

int bytesRead = inChannel.read(buf); //read into buffer.
//通過put方法寫B(tài)uffer的例子:
buf.put(127);

put方法有很多版本,允許你以不同的方式把數(shù)據(jù)寫入到Buffer中。例如, 寫到一個(gè)指定的位置,或者把一個(gè)字節(jié)數(shù)組寫入到Buffer。 更多Buffer實(shí)現(xiàn)的細(xì)節(jié)參考JavaDoc。

flip()方法
flip方法將Buffer從寫模式切換到讀模式。調(diào)用flip()方法會(huì)將position設(shè)回0,并將limit設(shè)置成之前position的值。
換句話說,position現(xiàn)在用于標(biāo)記讀的位置,limit表示之前寫進(jìn)了多少個(gè)byte、char等 —— 現(xiàn)在能讀取多少個(gè)byte、char等。

從Buffer中讀取數(shù)據(jù)從Buffer中讀取數(shù)據(jù)有兩種方式:從Buffer讀取數(shù)據(jù)到Channel。使用get()方法從Buffer中讀取數(shù)據(jù)。

2.4從Buffer讀取數(shù)據(jù)到Channel的例子:
//read from buffer into channel.
int bytesWritten = inChannel.write(buf);
//使用get()方法從Buffer中讀取數(shù)據(jù)的例子
byte aByte = buf.get();

get方法有很多版本,允許你以不同的方式從Buffer中讀取數(shù)據(jù)。
例如,從指定position讀取,或者從Buffer中讀取數(shù)據(jù)到字節(jié)數(shù)組。更多Buffer實(shí)現(xiàn)的細(xì)節(jié)參考JavaDoc。
rewind()方法
Buffer.rewind()將position設(shè)回0,所以你可以重讀Buffer中的所有數(shù)據(jù)。limit保持不變,仍然表示能從Buffer中讀取多少個(gè)元素(byte、char等)。
clear()與compact()方法
一旦讀完Buffer中的數(shù)據(jù),需要讓Buffer準(zhǔn)備好再次被寫入。可以通過clear()或compact()方法來完成。如果調(diào)用的是clear()方法,position將被設(shè)回0,limit被設(shè)置成 capacity的值。換句話說,Buffer 被清空了。Buffer中的數(shù)據(jù)并未清除,只是這些標(biāo)記告訴我們可以從哪里開始往Buffer里寫數(shù)據(jù)。如果Buffer中有一些未讀的數(shù)據(jù),調(diào)用clear()方法,數(shù)據(jù)將“被遺忘”,意味著不再有任何標(biāo)記會(huì)告訴你哪些數(shù)據(jù)被讀過,哪些還沒有。如果Buffer中仍有未讀的數(shù)據(jù),且后續(xù)還需要這些數(shù)據(jù),但是此時(shí)想要先先寫些數(shù)據(jù),那么使用compact()方法。compact()方法將所有未讀的數(shù)據(jù)拷貝到Buffer起始處。然后將position設(shè)到最后一個(gè)未讀元素正后面。limit屬性依然像clear()方法一樣,設(shè)置成capacity。現(xiàn)在Buffer準(zhǔn)備好寫數(shù)據(jù)了,但是不會(huì)覆蓋未讀的數(shù)據(jù)。mark()與reset()方法通過調(diào)用Buffer.mark()方法,可以標(biāo)記Buffer中的一個(gè)特定position。之后可以通過調(diào)用Buffer.reset()方法恢復(fù)到這個(gè)position。例如:

buffer.mark();
//call buffer.get() a couple of times, e.g. during parsing.
buffer.reset(); 
//set position back to mark.
3 Scatter/Gather

分散(scatter)從Channel中讀取是指在讀操作時(shí)將讀取的數(shù)據(jù)寫入多個(gè)buffer中。因此,Channel將從Channel中讀取的數(shù)據(jù)“分散(scatter)”到多個(gè)Buffer中。

ByteBuffer header = ByteBuffer.allocate(128);
ByteBuffer body = ByteBuffer.allocate(1024);
ByteBuffer[] bufferArray = { header, body };
channel.read(bufferArray);

聚集(gather)寫入Channel是指在寫操作時(shí)將多個(gè)buffer的數(shù)據(jù)寫入同一個(gè)Channel,因此,Channel 將多個(gè)Buffer中的數(shù)據(jù)“聚集(gather)”后發(fā)送到Channel。

ByteBuffer header = ByteBuffer.allocate(128);
ByteBuffer body = ByteBuffer.allocate(1024);
//write data into buffers
ByteBuffer[] bufferArray = { header, body };
channel.write(bufferArray);
4 通道之間的數(shù)據(jù)傳輸

在Java NIO中,如果兩個(gè)通道中有一個(gè)是FileChannel,那你可以直接將數(shù)據(jù)從一個(gè)channel(譯者注:channel中文常譯作通道)傳輸?shù)搅硗庖粋€(gè)channel。

RandomAccessFile fromFile = new RandomAccessFile("fromFile.txt", "rw");
FileChannel fromChannel = fromFile.getChannel();
RandomAccessFile toFile = new RandomAccessFile("toFile.txt", "rw");
FileChannel toChannel = toFile.getChannel();
long position = 0;
long count = fromChannel.size();
toChannel.transferFrom(position, count, fromChannel);
RandomAccessFile fromFile = new RandomAccessFile("fromFile.txt", "rw");
FileChannel fromChannel = fromFile.getChannel();
RandomAccessFile toFile = new RandomAccessFile("toFile.txt", "rw");
FileChannel toChannel = toFile.getChannel();
long position = 0;
long count = fromChannel.size();
fromChannel.transferTo(position, count, toChannel);
5 Selector

Selector可以在一個(gè)線程中管理多個(gè)通道。

5.1 創(chuàng)建和注冊(cè)

創(chuàng)建

Selector selector = Selector.open();

注冊(cè)

channel.configureBlocking(false);
SelectionKey key = channel.register(selector,Selectionkey.OP_READ);

與Selector一起使用時(shí),Channel必須處于非阻塞模式下。這意味著不能將FileChannel與Selector一起使用,因?yàn)镕ileChannel不能切換到非阻塞模式。而套接字通道都可以。
Selector可以監(jiān)聽四種事件,這四種事件用SelectionKey的四個(gè)常量來表示:SelectionKey.OP_CONNECT
SelectionKey.OP_ACCEPT
SelectionKey.OP_READ
SelectionKey.OP_WRITE
如果你對(duì)不止一種事件感興趣,那么可以用“位或”操作符將常量連接起來,如下:

int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;
5.2 SelectionKey

SelectionKey中包含了以下屬性:
interest集合

int interestSet = selectionKey.interestOps();
boolean isInterestedInAccept = (interestSet & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT;
boolean isInterestedInConnect = interestSet & SelectionKey.OP_CONNECT;
boolean isInterestedInRead = interestSet & SelectionKey.OP_READ;
boolean isInterestedInWrite = interestSet & SelectionKey.OP_WRITE;

ready集合
ready集合表示通道已經(jīng)準(zhǔn)備就緒的操作集合,可以以如下的方式訪問ready集合:

int readySet = selectionKey.readyOps();

也可以

selectionKey.isAcceptable();
selectionKey.isConnectable();
selectionKey.isReadable();
selectionKey.isWritable();

Channel + Selector
從SelectionKey訪問Channel和Selector很簡單。如下:

Channel channel = selectionKey.channel();
Selector selector = selectionKey.selector();

附加的對(duì)象
可以將一個(gè)對(duì)象或者更多信息附著到SelectionKey上,這樣就能方便的識(shí)別某個(gè)給定的通道。例如,可以附加 與通道一起使用的Buffer,或是包含聚集數(shù)據(jù)的某個(gè)對(duì)象。使用方法如下:

selectionKey.attach(theObject);
Object attachedObj = selectionKey.attachment();

還可以在用register()方法向Selector注冊(cè)Channel的時(shí)候附加對(duì)象。如:
SelectionKey key = channel.register(selector, SelectionKey.OP_READ, theObject);

5.3 通過Selector選擇通道

一旦向Selector注冊(cè)了一或多個(gè)通道,就可以調(diào)用幾個(gè)重載的select()方法。這些方法返回你所感興趣的事件(如連接、接受、讀或?qū)懀┮呀?jīng)準(zhǔn)備就緒的那些通道。換句話說,如果你對(duì)“讀就緒”的通道感興趣,select()方法會(huì)返回讀事件已經(jīng)就緒的那些通道。
下面是select()方法:

int select()
int select(long timeout)
int selectNow()

當(dāng)像Selector注冊(cè)Channel時(shí),Channel.register()方法會(huì)返回一個(gè)SelectionKey 對(duì)象。這個(gè)對(duì)象代表了注冊(cè)到該Selector的通道。可以通過SelectionKey的selectedKeySet()方法訪問這些對(duì)象。

Set selectedKeys = selector.selectedKeys();
Iterator keyIterator = selectedKeys.iterator();
while(keyIterator.hasNext()) 
    { SelectionKey key = keyIterator.next();
      if(key.isAcceptable())
          { // a connection was accepted by a ServerSocketChannel. 

          } 
      else if (key.isConnectable()) 
          { // a connection was established with a remote server.
          } 
      else if (key.isReadable()) 
          { // a channel is ready for reading
          }
      else if (key.isWritable()) 
          { // a channel is ready for writing
          } 
      keyIterator.remove();
}

這個(gè)循環(huán)遍歷已選擇鍵集中的每個(gè)鍵,并檢測(cè)各個(gè)鍵所對(duì)應(yīng)的通道的就緒事件。
注意每次迭代末尾的keyIterator.remove()調(diào)用。Selector不會(huì)自己從已選擇鍵集中移除SelectionKey實(shí)例。必須在處理完通道時(shí)自己移除。下次該通道變成就緒時(shí),Selector會(huì)再次將其放入已選擇鍵集中。

SelectionKey.channel()方法返回的通道需要轉(zhuǎn)型成你要處理的類型,如ServerSocketChannel或SocketChannel等。

5.4wakeUp()

某個(gè)線程調(diào)用select()方法后阻塞了,即使沒有通道已經(jīng)就緒,也有辦法讓其從select()方法返回。只要讓其它線程在第一個(gè)線程調(diào)用select()方法的那個(gè)對(duì)象上調(diào)用Selector.wakeup()方法即可。阻塞在select()方法上的線程會(huì)立馬返回。
如果有其它線程調(diào)用了wakeup()方法,但當(dāng)前沒有線程阻塞在select()方法上,下個(gè)調(diào)用select()方法的線程會(huì)立即“醒來(wake up)”。

5.5 close()

用完Selector后調(diào)用其close()方法會(huì)關(guān)閉該Selector,且使注冊(cè)到該Selector上的所有SelectionKey實(shí)例無效。通道本身并不會(huì)關(guān)閉。

5.6 完整的示例
Selector selector = Selector.open();
channel.configureBlocking(false);
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
while(true)
    { int readyChannels = selector.select(); 
         if(readyChannels == 0) continue;
         Set selectedKeys = selector.selectedKeys(); 
          Iterator keyIterator = selectedKeys.iterator();
          while(keyIterator.hasNext()) 
            { 
                   SelectionKey key = keyIterator.next();
                    if(key.isAcceptable()) 
                          { // a connection was accepted by a ServerSocketChannel. }
                   else if (key.isConnectable()) 
                         { // a connection was established with a remote server. } 
                   else if (key.isReadable()) // a channel is ready for reading } 
                   else if (key.isWritable()) { // a channel is ready for writing } 
                  keyIterator.remove();
               }
    }
6 FileChannel

Java NIO中的FileChannel是一個(gè)連接到文件的通道。可以通過文件通道讀寫文件。FileChannel無法設(shè)置為非阻塞模式,它總是運(yùn)行在阻塞模式下。
打開FileChannel
在使用FileChannel之前,必須先打開它。但是,我們無法直接打開一個(gè)FileChannel,需要通過使用一個(gè)InputStream、OutputStream或RandomAccessFile來獲取一個(gè)FileChannel實(shí)例。下面是通過RandomAccessFile打開FileChannel的示例:
RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt","rw");FileChannel inChannel = aFile.getChannel();

從FileChannel讀取數(shù)據(jù)
調(diào)用多個(gè)read()方法之一從FileChannel中讀取數(shù)據(jù)。如:
ByteBuffer buf = ByteBuffer.allocate(48);int bytesRead = inChannel.read(buf);

首先,分配一個(gè)Buffer。從FileChannel中讀取的數(shù)據(jù)將被讀到Buffer中。然后,調(diào)用FileChannel.read()方法。該方法將數(shù)據(jù)從FileChannel讀取到Buffer中。read()方法返回的int值表示了有多少字節(jié)被讀到了Buffer中。如果返回-1,表示到了文件末尾。

向FileChannel寫數(shù)據(jù)
使用FileChannel.write()方法向FileChannel寫數(shù)據(jù),該方法的參數(shù)是一個(gè)Buffer。如:

String newData = "New String to write to file..." + System.currentTimeMillis();
ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
buf.put(newData.getBytes());
buf.flip();
while(buf.hasRemaining()) 
{
 channel.write(buf);
}

注意FileChannel.write()是在while循環(huán)中調(diào)用的。因?yàn)闊o法保證write()方法一次能向FileChannel寫入多少字節(jié),因此需要重復(fù)調(diào)用write()方法,直到Buffer中已經(jīng)沒有尚未寫入通道的字節(jié)。
關(guān)閉FileChannel
用完FileChannel后必須將其關(guān)閉。如:

channel.close();

FileChannel的position方法
有時(shí)可能需要在FileChannel的某個(gè)特定位置進(jìn)行數(shù)據(jù)的讀/寫操作。可以通過調(diào)用position()方法獲取FileChannel的當(dāng)前位置。也可以通過調(diào)用position(long pos)方法設(shè)置FileChannel的當(dāng)前位置。這里有兩個(gè)例子:

long pos = channel.position();
channel.position(pos +123);

如果將位置設(shè)置在文件結(jié)束符之后,然后試圖從文件通道中讀取數(shù)據(jù),讀方法將返回-1 —— 文件結(jié)束標(biāo)志。如果將位置設(shè)置在文件結(jié)束符之后,然后向通道中寫數(shù)據(jù),文件將撐大到當(dāng)前位置并寫入數(shù)據(jù)。這可能導(dǎo)致“文件空洞”,磁盤上物理文件中寫入的數(shù)據(jù)間有空隙。FileChannel的size方法FileChannel實(shí)例的size()方法將返回該實(shí)例所關(guān)聯(lián)文件的大小。如:

long fileSize = channel.size();

FileChannel的truncate方法可以使用FileChannel.truncate()方法截取一個(gè)文件。截取文件時(shí),文件將中指定長度后面的部分將被刪除。如:

channel.truncate(1024);

這個(gè)例子截取文件的前1024個(gè)字節(jié)。FileChannel的force方法FileChannel.force()方法將通道里尚未寫入磁盤的數(shù)據(jù)強(qiáng)制寫到磁盤上。出于性能方面的考慮,操作系統(tǒng)會(huì)將數(shù)據(jù)緩存在內(nèi)存中,所以無法保證寫入到FileChannel里的數(shù)據(jù)一定會(huì)即時(shí)寫到磁盤上。要保證這一點(diǎn),需要調(diào)用force()方法。force()方法有一個(gè)boolean類型的參數(shù),指明是否同時(shí)將文件元數(shù)據(jù)(權(quán)限信息等)寫到磁盤上。下面的例子同時(shí)將文件數(shù)據(jù)和元數(shù)據(jù)強(qiáng)制寫到磁盤上:

channel.force(true);
7 SocketChannel

Java NIO中的SocketChannel是一個(gè)連接到TCP網(wǎng)絡(luò)套接字的通道。可以通過以下2種方式創(chuàng)建SocketChannel:打開一個(gè)SocketChannel并連接到互聯(lián)網(wǎng)上的某臺(tái)服務(wù)器。一個(gè)新連接到達(dá)ServerSocketChannel時(shí),會(huì)創(chuàng)建一個(gè)SocketChannel。

打開 SocketChannel
下面是SocketChannel的打開方式:

SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("[http://jenkov.com](http://jenkov.com/)", 80));

關(guān)閉 SocketChannel
當(dāng)用完SocketChannel之后調(diào)用SocketChannel.close()關(guān)閉SocketChannel:

socketChannel.close();

從 SocketChannel 讀取數(shù)據(jù)
要從SocketChannel中讀取數(shù)據(jù),調(diào)用一個(gè)read()的方法之一。以下是例子:

ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = socketChannel.read(buf);

首先,分配一個(gè)Buffer。從SocketChannel讀取到的數(shù)據(jù)將會(huì)放到這個(gè)Buffer中。然后,調(diào)用SocketChannel.read()。該方法將數(shù)據(jù)從SocketChannel 讀到Buffer中。read()方法返回的int值表示讀了多少字節(jié)進(jìn)Buffer里。如果返回的是-1,表示已經(jīng)讀到了流的末尾(連接關(guān)閉了)。

寫入 SocketChannel
寫數(shù)據(jù)到SocketChannel用的是SocketChannel.write()方法,該方法以一個(gè)Buffer作為參數(shù)。示例如下:

String newData = "New String to write to file..."+ System.currentTimeMillis();
ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();buf.put(newData.getBytes());
buf.flip();
while(buf.hasRemaining()) 
{
 channel.write(buf);
}

注意SocketChannel.write()方法的調(diào)用是在一個(gè)while循環(huán)中的。Write()方法無法保證能寫多少字節(jié)到SocketChannel。所以,我們重復(fù)調(diào)用write()直到Buffer沒有要寫的字節(jié)為止。
非阻塞模式
可以設(shè)置 SocketChannel 為非阻塞模式(non-blocking mode).設(shè)置之后,就可以在異步模式下調(diào)用connect(), read() 和write()了。

1.connect()
如果SocketChannel在非阻塞模式下,此時(shí)調(diào)用connect(),該方法可能在連接建立之前就返回了。為了確定連接是否建立,可以調(diào)用finishConnect()的方法。像這樣:

socketChannel.configureBlocking(false);
socketChannel.connect(new InetSocketAddress("[http://jenkov.com](http://jenkov.com/)", 80));
while(! socketChannel.finishConnect() )
{
  //wait, or do something else...
}

2.write()
非阻塞模式下,write()方法在尚未寫出任何內(nèi)容時(shí)可能就返回了。所以需要在循環(huán)中調(diào)用write()。前面已經(jīng)有例子了,這里就不贅述了。3.read()非阻塞模式下,read()方法在尚未讀取到任何數(shù)據(jù)時(shí)可能就返回了。所以需要關(guān)注它的int返回值,它會(huì)告訴你讀取了多少字節(jié)。
非阻塞模式與選擇器
非阻塞模式與選擇器搭配會(huì)工作的更好,通過將一或多個(gè)SocketChannel注冊(cè)到Selector,可以詢問選擇器哪個(gè)通道已經(jīng)準(zhǔn)備好了讀取,寫入等。Selector與SocketChannel的搭配使用會(huì)在后面詳講。

8 ServerSocketChannel

Java NIO中的 ServerSocketChannel 是一個(gè)可以監(jiān)聽新進(jìn)來的TCP連接的通道, 就像標(biāo)準(zhǔn)IO中的ServerSocket一樣。ServerSocketChannel類在 java.nio.channels包中。

ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress(9999));
while(true)
{ SocketChannel socketChannel = serverSocketChannel.accept(); 
//do something with socketChannel...
}

非阻塞模式
ServerSocketChannel可以設(shè)置成非阻塞模式。在非阻塞模式下,accept() 方法會(huì)立刻返回,如果還沒有新進(jìn)來的連接,返回的將是null。 因此,需要檢查返回的SocketChannel是否是null.如:

ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress(9999));
serverSocketChannel.configureBlocking(false);
while(true){ 
    SocketChannel socketChannel = serverSocketChannel.accept(); 
    if(socketChannel != null)
         {
              //do something with socketChannel... 
         }
}
9 DatagramChannel

Java NIO中的DatagramChannel是一個(gè)能收發(fā)UDP包的通道。因?yàn)閁DP是無連接的網(wǎng)絡(luò)協(xié)議,所以不能像其它通道那樣讀取和寫入。它發(fā)送和接收的是數(shù)據(jù)包。
打開 DatagramChannel
下面是 DatagramChannel 的打開方式:

DatagramChannel channel =DatagramChannel.open();
channel.socket().bind(new InetSocketAddress(9999));

這個(gè)例子打開的 DatagramChannel可以在UDP端口9999上接收數(shù)據(jù)包。
接收數(shù)據(jù)通過receive()方法從DatagramChannel接收數(shù)據(jù),如:

ByteBuffer buf =ByteBuffer.allocate(48);
buf.clear();
channel.receive(buf);

receive()方法會(huì)將接收到的數(shù)據(jù)包內(nèi)容復(fù)制到指定的Buffer. 如果Buffer容不下收到的數(shù)據(jù),多出的數(shù)據(jù)將被丟棄。
發(fā)送數(shù)據(jù)
通過send()方法從DatagramChannel發(fā)送數(shù)據(jù),如:

String newData = "New String to write to file..." + System.currentTimeMillis();
ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
buf.put(newData.getBytes());
buf.flip();
int bytesSent = channel.send(buf, new InetSocketAddress("jenkov.com", 80));

這個(gè)例子發(fā)送一串字符到”jenkov.com”服務(wù)器的UDP端口80。 因?yàn)榉?wù)端并沒有監(jiān)控這個(gè)端口,所以什么也不會(huì)發(fā)生。也不會(huì)通知你發(fā)出的數(shù)據(jù)包是否已收到,因?yàn)閁DP在數(shù)據(jù)傳送方面沒有任何保證。

連接到特定的地址
可以將DatagramChannel“連接”到網(wǎng)絡(luò)中的特定地址的。由于UDP是無連接的,連接到特定地址并不會(huì)像TCP通道那樣創(chuàng)建一個(gè)真正的連接。而是鎖住DatagramChannel ,讓其只能從特定地址收發(fā)數(shù)據(jù)。這里有個(gè)例子:

channel.connect(new InetSocketAddress("jenkov.com", 80));

當(dāng)連接后,也可以使用read()和write()方法,就像在用傳統(tǒng)的通道一樣。只是在數(shù)據(jù)傳送方面沒有任何保證。這里有幾個(gè)例子:

int bytesRead = channel.read(buf);int bytesWritten = channel.write(but);
10 Pipe

Java NIO 管道是2個(gè)線程之間的單向數(shù)據(jù)連接。Pipe有一個(gè)source通道和一個(gè)sink通道。數(shù)據(jù)會(huì)被寫到sink通道,從source通道讀取。這里是Pipe原理的圖示:


創(chuàng)建管道通過Pipe.open()方法打開管道。例如:

Pipe pipe = Pipe.open();

向管道寫數(shù)據(jù)
要向管道寫數(shù)據(jù),需要訪問sink通道。像這樣:

Pipe.SinkChannel sinkChannel = pipe.sink();

通過調(diào)用SinkChannel的write()方法,將數(shù)據(jù)寫入SinkChannel,像這樣:

String newData = "New String to write to file..." + System.currentTimeMillis();
ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
buf.put(newData.getBytes());
buf.flip();
while(buf.hasRemaining())
{ 
   sinkChannel.write(buf);
}

從管道讀取數(shù)據(jù)
從讀取管道的數(shù)據(jù),需要訪問source通道,像這樣:

Pipe.SourceChannel sourceChannel = pipe.source();

調(diào)用source通道的read()方法來讀取數(shù)據(jù),像這樣:

ByteBuffer buf = ByteBuffer.allocate(48);int bytesRead = sourceChannel.read(buf);

read()方法返回的int值會(huì)告訴我們多少字節(jié)被讀進(jìn)了緩沖區(qū)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,797評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,179評(píng)論 3 414
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,628評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,642評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,444評(píng)論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,948評(píng)論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,040評(píng)論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,185評(píng)論 0 287
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,717評(píng)論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,602評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,794評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,316評(píng)論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,045評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,418評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,671評(píng)論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,414評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,750評(píng)論 2 370

推薦閱讀更多精彩內(nèi)容

  • Java NIO(New IO)是從Java 1.4版本開始引入的一個(gè)新的IO API,可以替代標(biāo)準(zhǔn)的Java I...
    JackChen1024閱讀 7,562評(píng)論 1 143
  • (轉(zhuǎn)載說明:本文非原創(chuàng),轉(zhuǎn)載自http://ifeve.com/java-nio-all/) Java NIO: ...
    柳岸閱讀 825評(píng)論 0 3
  • 簡介 Java NIO 是由 Java 1.4 引進(jìn)的異步 IO.Java NIO 由以下幾個(gè)核心部分組成: Ch...
    永順閱讀 1,807評(píng)論 0 15
  • 前言: 之前的文章《Java文件IO常用歸納》主要寫了Java 標(biāo)準(zhǔn)IO要注意的細(xì)節(jié)和技巧,由于網(wǎng)上各種學(xué)習(xí)途徑,...
    androidjp閱讀 2,912評(píng)論 0 22
  • 作者: 一字馬胡 轉(zhuǎn)載標(biāo)志 【2017-11-24】 更新日志 一、Java OIO Java OIO (Jav...
    一字馬胡閱讀 1,369評(píng)論 0 12