Java NIO

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

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

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

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

1 Channel

通道的讀寫是雙向的,可異步的,并且都需要通過Buffer來進行。這些是Java NIO中最重要的通道的實現(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)聽新進來的TCP連接,像Web服務(wù)器那樣。對每一個新進來的連接都會創(chuàng)建一個SocketChannel。

2 Buffer

首先看個例子

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

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

limit
在寫模式下,Buffer的limit表示你最多能往Buffer里寫多少數(shù)據(jù)。 寫模式下,limit等于Buffer的capacity。當切換Buffer到讀模式時, limit表示你最多能讀到多少數(shù)據(jù)。因此,當切換Buffer到讀模式時,limit會被設(shè)置成寫模式下的position值。換句話說,你能讀到之前寫入的所有數(shù)據(jù)(limit被設(shè)置成已寫數(shù)據(jù)的數(shù)量,這個值在寫模式下就是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的分配

要想獲得一個Buffer對象首先要進行分配。 每一個Buffer類都有一個allocate方法。下面是一個分配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中。例如, 寫到一個指定的位置,或者把一個字節(jié)數(shù)組寫入到Buffer。 更多Buffer實現(xiàn)的細節(jié)參考JavaDoc。

flip()方法
flip方法將Buffer從寫模式切換到讀模式。調(diào)用flip()方法會將position設(shè)回0,并將limit設(shè)置成之前position的值。
換句話說,position現(xiàn)在用于標記讀的位置,limit表示之前寫進了多少個byte、char等 —— 現(xiàn)在能讀取多少個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實現(xiàn)的細節(jié)參考JavaDoc。
rewind()方法
Buffer.rewind()將position設(shè)回0,所以你可以重讀Buffer中的所有數(shù)據(jù)。limit保持不變,仍然表示能從Buffer中讀取多少個元素(byte、char等)。
clear()與compact()方法
一旦讀完Buffer中的數(shù)據(jù),需要讓Buffer準備好再次被寫入。可以通過clear()或compact()方法來完成。如果調(diào)用的是clear()方法,position將被設(shè)回0,limit被設(shè)置成 capacity的值。換句話說,Buffer 被清空了。Buffer中的數(shù)據(jù)并未清除,只是這些標記告訴我們可以從哪里開始往Buffer里寫數(shù)據(jù)。如果Buffer中有一些未讀的數(shù)據(jù),調(diào)用clear()方法,數(shù)據(jù)將“被遺忘”,意味著不再有任何標記會告訴你哪些數(shù)據(jù)被讀過,哪些還沒有。如果Buffer中仍有未讀的數(shù)據(jù),且后續(xù)還需要這些數(shù)據(jù),但是此時想要先先寫些數(shù)據(jù),那么使用compact()方法。compact()方法將所有未讀的數(shù)據(jù)拷貝到Buffer起始處。然后將position設(shè)到最后一個未讀元素正后面。limit屬性依然像clear()方法一樣,設(shè)置成capacity。現(xiàn)在Buffer準備好寫數(shù)據(jù)了,但是不會覆蓋未讀的數(shù)據(jù)。mark()與reset()方法通過調(diào)用Buffer.mark()方法,可以標記Buffer中的一個特定position。之后可以通過調(diào)用Buffer.reset()方法恢復(fù)到這個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ù)據(jù)寫入多個buffer中。因此,Channel將從Channel中讀取的數(shù)據(jù)“分散(scatter)”到多個Buffer中。

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

聚集(gather)寫入Channel是指在寫操作時將多個buffer的數(shù)據(jù)寫入同一個Channel,因此,Channel 將多個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中,如果兩個通道中有一個是FileChannel,那你可以直接將數(shù)據(jù)從一個channel(譯者注:channel中文常譯作通道)傳輸?shù)搅硗庖粋€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可以在一個線程中管理多個通道。

5.1 創(chuàng)建和注冊

創(chuàng)建

Selector selector = Selector.open();

注冊

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

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

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)準備就緒的操作集合,可以以如下的方式訪問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();

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

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

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

5.3 通過Selector選擇通道

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

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

當像Selector注冊Channel時,Channel.register()方法會返回一個SelectionKey 對象。這個對象代表了注冊到該Selector的通道。可以通過SelectionKey的selectedKeySet()方法訪問這些對象。

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();
}

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

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

5.4wakeUp()

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

5.5 close()

用完Selector后調(diào)用其close()方法會關(guān)閉該Selector,且使注冊到該Selector上的所有SelectionKey實例無效。通道本身并不會關(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是一個連接到文件的通道。可以通過文件通道讀寫文件。FileChannel無法設(shè)置為非阻塞模式,它總是運行在阻塞模式下。
打開FileChannel
在使用FileChannel之前,必須先打開它。但是,我們無法直接打開一個FileChannel,需要通過使用一個InputStream、OutputStream或RandomAccessFile來獲取一個FileChannel實例。下面是通過RandomAccessFile打開FileChannel的示例:
RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt","rw");FileChannel inChannel = aFile.getChannel();

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

首先,分配一個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ù)是一個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)用的。因為無法保證write()方法一次能向FileChannel寫入多少字節(jié),因此需要重復(fù)調(diào)用write()方法,直到Buffer中已經(jīng)沒有尚未寫入通道的字節(jié)。
關(guān)閉FileChannel
用完FileChannel后必須將其關(guān)閉。如:

channel.close();

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

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

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

long fileSize = channel.size();

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

channel.truncate(1024);

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

channel.force(true);
7 SocketChannel

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

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

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

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

socketChannel.close();

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

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

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

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

8 ServerSocketChannel

Java NIO中的 ServerSocketChannel 是一個可以監(jiān)聽新進來的TCP連接的通道, 就像標準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() 方法會立刻返回,如果還沒有新進來的連接,返回的將是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是一個能收發(fā)UDP包的通道。因為UDP是無連接的網(wǎng)絡(luò)協(xié)議,所以不能像其它通道那樣讀取和寫入。它發(fā)送和接收的是數(shù)據(jù)包。
打開 DatagramChannel
下面是 DatagramChannel 的打開方式:

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

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

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

receive()方法會將接收到的數(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));

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

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

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

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

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

Java NIO 管道是2個線程之間的單向數(shù)據(jù)連接。Pipe有一個source通道和一個sink通道。數(shù)據(jù)會被寫到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值會告訴我們多少字節(jié)被讀進了緩沖區(qū)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

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