Java NIO系列教程(三) Channel之Socket通道

一、Socket通道

  新的socket通道類可以運(yùn)行非阻塞模式并且是可選擇的。這兩個(gè)性能可以激活大程序(如網(wǎng)絡(luò)服務(wù)器和中間件組件)巨大的可伸縮性和靈活性。本節(jié)中我們會(huì)看到,再也沒有為每個(gè)socket連接使用一個(gè)線程的必要了,也避免了管理大量線程所需的上下文交換總開銷。借助新的NIO類,一個(gè)或幾個(gè)線程就可以管理成百上千的活動(dòng)socket連接了并且只有很少甚至可能沒有性能損失。所有的socket通道類(DatagramChannel、SocketChannel和ServerSocketChannel)都繼承了位于java.nio.channels.spi包中的AbstractSelectableChannel。這意味著我們可以用一個(gè)Selector對象來執(zhí)行socket通道的就緒選擇(readiness selection)。

請注意DatagramChannel和SocketChannel實(shí)現(xiàn)定義讀和寫功能的接口而ServerSocketChannel不實(shí)現(xiàn)。ServerSocketChannel負(fù)責(zé)監(jiān)聽傳入的連接和創(chuàng)建新的SocketChannel對象,它本身從不傳輸數(shù)據(jù)。

  在我們具體討論每一種socket通道前,您應(yīng)該了解socket和socket通道之間的關(guān)系。之前的章節(jié)中有寫道,通道是一個(gè)連接I/O服務(wù)導(dǎo)管并提供與該服務(wù)交互的方法。就某個(gè)socket而言,它不會(huì)再次實(shí)現(xiàn)與之對應(yīng)的socket通道類中的socket協(xié)議API,而java.net中已經(jīng)存在的socket通道都可以被大多數(shù)協(xié)議操作重復(fù)使用。

全部socket通道類(DatagramChannel、SocketChannel和ServerSocketChannel)在被實(shí)例化時(shí)都會(huì)創(chuàng)建一個(gè)對等socket對象。這些是我們所熟悉的來自java.net的類(Socket、ServerSocket和DatagramSocket),它們已經(jīng)被更新以識(shí)別通道。對等socket可以通過調(diào)用socket( )方法從一個(gè)通道上獲取。此外,這三個(gè)java.net類現(xiàn)在都有g(shù)etChannel( )方法。

Socket通道將與通信協(xié)議相關(guān)的操作委托給相應(yīng)的socket對象。socket的方法看起來好像在通道類中重復(fù)了一遍,但實(shí)際上通道類上的方法會(huì)有一些新的或者不同的行為。

  要把一個(gè)socket通道置于非阻塞模式,我們要依靠所有socket通道類的公有超級類:SelectableChannel。就緒選擇(readiness selection)是一種可以用來查詢通道的機(jī)制,該查詢可以判斷通道是否準(zhǔn)備好執(zhí)行一個(gè)目標(biāo)操作,如讀或?qū)憽7亲枞鸌/O和可選擇性是緊密相連的,那也正是管理阻塞模式的API代碼要在SelectableChannel超級類中定義的原因。

  設(shè)置或重新設(shè)置一個(gè)通道的阻塞模式是很簡單的,只要調(diào)用configureBlocking( )方法即可,傳遞參數(shù)值為true則設(shè)為阻塞模式,參數(shù)值為false值設(shè)為非阻塞模式。真的,就這么簡單!您可以通過調(diào)用isBlocking( )方法來判斷某個(gè)socket通道當(dāng)前處于哪種模式。

AbstractSelectableChannel.java中實(shí)現(xiàn)的configureBlocking()方法如下:

publicfinalSelectableChannel configureBlocking(boolean block)

? ? ? ? throws IOException

? ? {

? ? ? ? synchronized (regLock) {

? ? ? ? ? ? if(!isOpen())

? ? ? ? ? ? ? ? thrownew ClosedChannelException();

? ? ? ? ? ? if(blocking == block)

? ? ? ? ? ? ? ? returnthis;

? ? ? ? ? ? if(block && haveValidKeys())

? ? ? ? ? ? ? ? thrownew IllegalBlockingModeException();

? ? ? ? ? ? implConfigureBlocking(block);

? ? ? ? ? ? blocking = block;

? ? ? ? }

? ? ? ? returnthis;

? ? }

  非阻塞socket通常被認(rèn)為是服務(wù)端使用的,因?yàn)樗鼈兪雇瑫r(shí)管理很多socket通道變得更容易。但是,在客戶端使用一個(gè)或幾個(gè)非阻塞模式的socket通道也是有益處的,例如,借助非阻塞socket通道,GUI程序可以專注于用戶請求并且同時(shí)維護(hù)與一個(gè)或多個(gè)服務(wù)器的會(huì)話。在很多程序上,非阻塞模式都是有用的。

  偶爾地,我們也會(huì)需要防止socket通道的阻塞模式被更改。API中有一個(gè)blockingLock( )方法,該方法會(huì)返回一個(gè)非透明的對象引用。返回的對象是通道實(shí)現(xiàn)修改阻塞模式時(shí)內(nèi)部使用的。只有擁有此對象的鎖的線程才能更改通道的阻塞模式。

下面分別介紹這3個(gè)通道。

二、 ServerSocketChannel

讓我們從最簡單的ServerSocketChannel來開始對socket通道類的討論。以下是ServerSocketChannel的完整API:

publicabstractclassServerSocketChannelextends AbstractSelectableChannel

? {

? ? ? publicstaticServerSocketChannel open()throws IOException;

? ? ? publicabstract ServerSocket socket();

? ? ? publicabstractServerSocket accept()throws IOException;

? ? ? publicfinalint validOps();

? }

ServerSocketChannel是一個(gè)基于通道的socket監(jiān)聽器。它同我們所熟悉的java.net.ServerSocket執(zhí)行相同的基本任務(wù),不過它增加了通道語義,因此能夠在非阻塞模式下運(yùn)行。

由于ServerSocketChannel沒有bind()方法,因此有必要取出對等的socket并使用它來綁定到一個(gè)端口以開始監(jiān)聽連接。我們也是使用對等ServerSocket的API來根據(jù)需要設(shè)置其他的socket選項(xiàng)。

同它的對等體java.net.ServerSocket一樣,ServerSocketChannel也有accept( )方法。一旦您創(chuàng)建了一個(gè)ServerSocketChannel并用對等socket綁定了它,然后您就可以在其中一個(gè)上調(diào)用accept()。如果您選擇在ServerSocket上調(diào)用accept( )方法,那么它會(huì)同任何其他的ServerSocket表現(xiàn)一樣的行為:總是阻塞并返回一個(gè)java.net.Socket對象。如果您選擇在ServerSocketChannel上調(diào)用accept( )方法則會(huì)返回SocketChannel類型的對象,返回的對象能夠在非阻塞模式下運(yùn)行。

換句話說:

ServerSocketChannel的accept()方法會(huì)返回SocketChannel類型對象,SocketChannel可以在非阻塞模式下運(yùn)行。

其它Socket的accept()方法會(huì)阻塞返回一個(gè)Socket對象。

如果ServerSocketChannel以非阻塞模式被調(diào)用,當(dāng)沒有傳入連接在等待時(shí),ServerSocketChannel.accept( )會(huì)立即返回null。正是這種檢查連接而不阻塞的能力實(shí)現(xiàn)了可伸縮性并降低了復(fù)雜性??蛇x擇性也因此得到實(shí)現(xiàn)。我們可以使用一個(gè)選擇器實(shí)例來注冊一個(gè)ServerSocketChannel對象以實(shí)現(xiàn)新連接到達(dá)時(shí)自動(dòng)通知的功能。以下代碼演示了如何使用一個(gè)非阻塞的accept( )方法:

package com.dxz.springsession.nio.demo2;import java.nio.ByteBuffer;import java.nio.channels.ServerSocketChannel;import java.nio.channels.SocketChannel;import java.net.InetSocketAddress;publicclass ChannelAccept {

? ? publicstaticfinalString GREETING = "Hello I must be going.\r\n";

? ? publicstaticvoidmain(String[] argv)throws Exception {

? ? ? ? intport = 1234;// defaultif(argv.length > 0) {

? ? ? ? ? ? port = Integer.parseInt(argv[0]);

? ? ? ? }

? ? ? ? ByteBuffer buffer = ByteBuffer.wrap(GREETING.getBytes());

? ? ? ? ServerSocketChannel ssc = ServerSocketChannel.open();

? ? ? ? ssc.socket().bind(new InetSocketAddress(port));

? ? ? ? ssc.configureBlocking(false);

? ? ? ? while(true) {

? ? ? ? ? ? System.out.println("Waiting for connections");

? ? ? ? ? ? SocketChannel sc = ssc.accept();

? ? ? ? ? ? if(sc ==null) {

? ? ? ? ? ? ? ? System.out.println("null");

? ? ? ? ? ? ? ? Thread.sleep(2000);

? ? ? ? ? ? } else {

? ? ? ? ? ? ? ? System.out.println("Incoming connection from: " + sc.socket().getRemoteSocketAddress());

? ? ? ? ? ? ? ? buffer.rewind();

? ? ? ? ? ? ? ? sc.write(buffer);

? ? ? ? ? ? ? ? sc.close();

? ? ? ? ? ? }

? ? ? ? }

? ? }

}

日志:

2.1、打開 ServerSocketChannel

通過調(diào)用 ServerSocketChannel.open() 方法來打開ServerSocketChannel.如:

ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

2.2、關(guān)閉 ServerSocketChannel

通過調(diào)用ServerSocketChannel.close() 方法來關(guān)閉ServerSocketChannel. 如:

serverSocketChannel.close();

2.3、監(jiān)聽新進(jìn)來的連接

通過 ServerSocketChannel.accept() 方法監(jiān)聽新進(jìn)來的連接。當(dāng) accept()方法返回的時(shí)候,它返回一個(gè)包含新進(jìn)來的連接的 SocketChannel。因此, accept()方法會(huì)一直阻塞到有新連接到達(dá)。

通常不會(huì)僅僅只監(jiān)聽一個(gè)連接,在while循環(huán)中調(diào)用 accept()方法. 如下面的例子:

while(true){

? ? ? ? ? ? SocketChannel socketChannel = serverSocketChannel.accept();

? ? ? ? ? ? //...}

2.4、阻塞模式

會(huì)在SocketChannel sc = ssc.accept();這里阻塞住進(jìn)程。

2.5、非阻塞模式

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

ServerSocketChannel ssc = ServerSocketChannel.open();

? ? ? ? ssc.socket().bind(new InetSocketAddress(port));

? ? ? ? ssc.configureBlocking(false);

? ? ? ? while(true) {

? ? ? ? ? ? System.out.println("Waiting for connections");

? ? ? ? ? ? SocketChannel sc = ssc.accept();

? ? ? ? ? ? if(sc !=null) {


? ? ? ? ? ? }


三、SocketChannel

下面開始學(xué)習(xí)SocketChannel,它是使用最多的socket通道類:

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。

3.1、打開 SocketChannel

下面是SocketChannel的打開方式:

SocketChannel socketChannel = SocketChannel.open();

socketChannel.connect(newInetSocketAddress("http://jenkov.com", 80));

3.2、關(guān)閉 SocketChannel

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

socketChannel.close();

3.3、從 SocketChannel 讀取數(shù)據(jù)

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

ByteBuffer buf = ByteBuffer.allocate(48);intbytesRead = 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)閉了)。

3.4、寫入 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é)為止。

3.5、非阻塞模式

可以設(shè)置 SocketChannel 為非阻塞模式(non-blocking mode).設(shè)置之后,就可以在異步模式下調(diào)用connect(), read() 和write()了。

3.5.1、connect()

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

socketChannel.configureBlocking(false);

socketChannel.connect(newInetSocketAddress("http://jenkov.com", 80));

while(! socketChannel.finishConnect() ){

? ? //wait, or do something else...}

3.5.2、write()

非阻塞模式下,write()方法在尚未寫出任何內(nèi)容時(shí)可能就返回了。所以需要在循環(huán)中調(diào)用write()。前面已經(jīng)有例子了,這里就不贅述了。

3.5.3、read()

非阻塞模式下,read()方法在尚未讀取到任何數(shù)據(jù)時(shí)可能就返回了。所以需要關(guān)注它的int返回值,它會(huì)告訴你讀取了多少字節(jié)。

3.6、非阻塞模式與選擇器

非阻塞模式與選擇器搭配會(huì)工作的更好,通過將一或多個(gè)SocketChannel注冊到Selector,可以詢問選擇器哪個(gè)通道已經(jīng)準(zhǔn)備好了讀取,寫入等。Selector與SocketChannel的搭配使用會(huì)在后面詳講。

四、DatagramChannel

最后一個(gè)socket通道是DatagramChannel。正如SocketChannel對應(yīng)Socket,ServerSocketChannel對應(yīng)ServerSocket,每一個(gè)DatagramChannel對象也有一個(gè)關(guān)聯(lián)的DatagramSocket對象。不過原命名模式在此并未適用:“DatagramSocketChannel”顯得有點(diǎn)笨拙,因此采用了簡潔的“DatagramChannel”名稱。

正如SocketChannel模擬連接導(dǎo)向的流協(xié)議(如TCP/IP),DatagramChannel則模擬包導(dǎo)向的無連接協(xié)議(如UDP/IP)。

DatagramChannel是無連接的。每個(gè)數(shù)據(jù)報(bào)(datagram)都是一個(gè)自包含的實(shí)體,擁有它自己的目的地址及不依賴其他數(shù)據(jù)報(bào)的數(shù)據(jù)負(fù)載。與面向流的的socket不同,DatagramChannel可以發(fā)送單獨(dú)的數(shù)據(jù)報(bào)給不同的目的地址。同樣,DatagramChannel對象也可以接收來自任意地址的數(shù)據(jù)包。每個(gè)到達(dá)的數(shù)據(jù)報(bào)都含有關(guān)于它來自何處的信息(源地址)。

4.1、打開 DatagramChannel

下面是 DatagramChannel 的打開方式:

DatagramChannel channel = DatagramChannel.open();

? ? channel.socket().bind(newInetSocketAddress(9999));

這個(gè)例子打開的 DatagramChannel可以在UDP端口9999上接收數(shù)據(jù)包。

4.2、接收數(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ù)將被丟棄。

4.3、發(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();


? ? intbytesSent = channel.send(buf,newInetSocketAddress("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ù)傳送方面沒有任何保證。

4.4、連接到特定的地址

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

這里有個(gè)例子:

channel.connect(newInetSocketAddress("jenkov.com", 80));

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

intbytesRead = channel.read(buf);

? ? intbytesWritten = channel.write(but);

?完整示例:

package com.dxz.springsession.nio.demo3;importjava.nio.channels.*;importjava.nio.charset.*;importjava.net.*;importjava.io.*;importjava.util.*;importjava.nio.*;publicclass DatagramChannelServerDemo {

? ? // UDP協(xié)議服務(wù)端privateintport = 9975;

? ? DatagramChannel channel;

? ? privateCharset charset = Charset.forName("UTF-8");

? ? privateSelector selector =null;

? ? publicDatagramChannelServerDemo()throws IOException {

? ? ? ? try {

? ? ? ? ? ? selector = Selector.open();

? ? ? ? ? ? channel = DatagramChannel.open();

? ? ? ? } catch (Exception e) {

? ? ? ? ? ? selector =null;

? ? ? ? ? ? channel =null;

? ? ? ? ? ? System.out.println("超時(shí)");

? ? ? ? }

? ? ? ? System.out.println("服務(wù)器啟動(dòng)");

? ? }

? ? /* 編碼過程 */public ByteBuffer encode(String str) {

? ? ? ? return charset.encode(str);

? ? }

? ? /* 解碼過程 */public String decode(ByteBuffer bb) {

? ? ? ? return charset.decode(bb).toString();

? ? }

? ? /* 服務(wù)器服務(wù)方法 */publicvoidservice()throws IOException {

? ? ? ? if(channel ==null|| selector ==null)

? ? ? ? ? ? return;

? ? ? ? channel.configureBlocking(false);

? ? ? ? channel.socket().bind(new InetSocketAddress(port));

? ? ? ? // channel.write(ByteBuffer.wrap(new String("aaaa").getBytes()));? ? ? ? channel.register(selector, SelectionKey.OP_READ);

? ? ? ? /** 外循環(huán),已經(jīng)發(fā)生了SelectionKey數(shù)目 */while(selector.select() > 0) {

? ? ? ? ? ? System.out.println("有新channel加入");

? ? ? ? ? ? /* 得到已經(jīng)被捕獲了的SelectionKey的集合 */? ? ? ? ? ? Iterator iterator = selector.selectedKeys().iterator();

? ? ? ? ? ? while (iterator.hasNext()) {

? ? ? ? ? ? ? ? SelectionKey key =null;

? ? ? ? ? ? ? ? try {

? ? ? ? ? ? ? ? ? ? key = (SelectionKey) iterator.next();

? ? ? ? ? ? ? ? ? ? iterator.remove();

? ? ? ? ? ? ? ? ? ? if (key.isReadable()) {

? ? ? ? ? ? ? ? ? ? ? ? reveice(key);

? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? ? ? if (key.isWritable()) {

? ? ? ? ? ? ? ? ? ? ? ? // send(key);? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? } catch (IOException e) {

? ? ? ? ? ? ? ? ? ? e.printStackTrace();

? ? ? ? ? ? ? ? ? ? try {

? ? ? ? ? ? ? ? ? ? ? ? if(key !=null) {

? ? ? ? ? ? ? ? ? ? ? ? ? ? key.cancel();

? ? ? ? ? ? ? ? ? ? ? ? ? ? key.channel().close();

? ? ? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? ? ? } catch (ClosedChannelException cex) {

? ? ? ? ? ? ? ? ? ? ? ? e.printStackTrace();

? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? }

? ? ? ? ? ? }

? ? ? ? ? ? /* 內(nèi)循環(huán)完 */? ? ? ? }

? ? ? ? /* 外循環(huán)完 */? ? }

? ? /*? ? * 接收 用receive()讀IO 作為服務(wù)端一般不需要調(diào)用connect(),如果未調(diào)用<span style=

? ? * "font-family: Arial, Helvetica, sans-serif;">connect()時(shí)調(diào)</span><span

? ? * style="font-family: Arial, Helvetica, sans-serif;"

? ? * >用read()\write()讀寫,會(huì)報(bào)java.nio.channels</span> .NotYetConnectedException

? ? * 只有調(diào)用connect()之后,才能使用read和write.

? ? */synchronizedpublicvoidreveice(SelectionKey key)throws IOException {

? ? ? ? if(key ==null)

? ? ? ? ? ? return;

? ? ? ? // ***用channel.receive()獲取客戶端消息***//// :接收時(shí)需要考慮字節(jié)長度DatagramChannel sc = (DatagramChannel) key.channel();

? ? ? ? String content = "";

? ? ? ? // create buffer with capacity of 48 bytesByteBuffer buf = ByteBuffer.allocate(1024);// java里一個(gè)(utf-8)中文3字節(jié),gbk中文占2個(gè)字節(jié)? ? ? ? buf.clear();

? ? ? ? SocketAddress address = sc.receive(buf);// read into buffer. 返回客戶端的地址信息String clientAddress = address.toString().replace("/", "").split(":")[0];

? ? ? ? String clientPost = address.toString().replace("/", "").split(":")[1];

? ? ? ? buf.flip(); // make buffer ready for readwhile (buf.hasRemaining()) {

? ? ? ? ? ? buf.get(newbyte[buf.limit()]);// read 1 byte at a timecontent +=new String(buf.array());

? ? ? ? }

? ? ? ? buf.clear(); // make buffer ready for writingSystem.out.println("接收:" + content.trim());

? ? ? ? // 第一次發(fā);udp采用數(shù)據(jù)報(bào)模式,發(fā)送多少次,接收多少次ByteBuffer buf2 = ByteBuffer.allocate(65507);

? ? ? ? buf2.clear();

? ? ? ? buf2.put(

? ? ? ? ? ? ? ? "消息推送內(nèi)容 abc..UDP是一個(gè)非連接的協(xié)議,傳輸數(shù)據(jù)之前源端和終端不建立連接,當(dāng)它想傳送時(shí)就簡單地去抓取來自應(yīng)用程序的數(shù)據(jù),并盡可能快地把它扔到網(wǎng)絡(luò)上。在發(fā)送端UDP是一個(gè)非連接的協(xié)議,傳輸數(shù)據(jù)之前源端和終端不建立連接,當(dāng)它想傳送時(shí)就簡單地去抓取來自應(yīng)用程序的數(shù)據(jù),并盡可能快地把它扔到網(wǎng)絡(luò)上。在發(fā)送端UDP是一個(gè)非連接的協(xié)議,傳輸數(shù)據(jù)之前源端和終端不建立連接,當(dāng)它想傳送時(shí)就簡單地去抓取來自應(yīng)用程序的數(shù)據(jù),并盡可能快地把它扔到網(wǎng)絡(luò)上。在發(fā)送端@Q"? ? ? ? ? ? ? ? ? ? ? ? .getBytes());

? ? ? ? buf2.flip();

? ? ? ? channel.send(buf2, newInetSocketAddress(clientAddress, Integer.parseInt(clientPost)));// 將消息回送給客戶端

? ? ? ? // 第二次發(fā)ByteBuffer buf3 = ByteBuffer.allocate(65507);

? ? ? ? buf3.clear();

? ? ? ? buf3.put("任務(wù)完成".getBytes());

? ? ? ? buf3.flip();

? ? ? ? channel.send(buf3, newInetSocketAddress(clientAddress, Integer.parseInt(clientPost)));// 將消息回送給客戶端? ? }

? ? inty = 0;

? ? publicvoid send(SelectionKey key) {

? ? ? ? if(key ==null)

? ? ? ? ? ? return;

? ? ? ? // ByteBuffer buff = (ByteBuffer) key.attachment();DatagramChannel sc = (DatagramChannel) key.channel();

? ? ? ? try {

? ? ? ? ? ? sc.write(ByteBuffer.wrap(newString("aaaa").getBytes()));

? ? ? ? } catch (IOException e1) {

? ? ? ? ? ? e1.printStackTrace();

? ? ? ? }

? ? ? ? System.out.println("send2() " + (++y));

? ? }

? ? /* 發(fā)送文件 */publicvoid sendFile(SelectionKey key) {

? ? ? ? if(key ==null)

? ? ? ? ? ? return;

? ? ? ? ByteBuffer buff = (ByteBuffer) key.attachment();

? ? ? ? SocketChannel sc = (SocketChannel) key.channel();

? ? ? ? String data = decode(buff);

? ? ? ? if(data.indexOf("get") == -1)

? ? ? ? ? ? return;

? ? ? ? String subStr = data.substring(data.indexOf(" "), data.length());

? ? ? ? System.out.println("截取之后的字符串是 " + subStr);

? ? ? ? FileInputStream fileInput =null;

? ? ? ? try {

? ? ? ? ? ? fileInput =new FileInputStream(subStr);

? ? ? ? ? ? FileChannel fileChannel = fileInput.getChannel();

? ? ? ? ? ? fileChannel.transferTo(0, fileChannel.size(), sc);

? ? ? ? ? ? fileChannel.close();

? ? ? ? } catch (IOException e) {

? ? ? ? ? ? e.printStackTrace();

? ? ? ? } finally {

? ? ? ? ? ? try {

? ? ? ? ? ? ? ? fileInput.close();

? ? ? ? ? ? } catch (IOException ex) {

? ? ? ? ? ? ? ? ex.printStackTrace();

? ? ? ? ? ? }

? ? ? ? }

? ? }

? ? publicstaticvoidmain(String[] args)throws IOException {

? ? ? ? new DatagramChannelServerDemo().service();

? ? }

}//客戶端package com.dxz.springsession.nio.demo3;importjava.nio.channels.*;importjava.nio.charset.*;importjava.net.*;importjava.io.*;importjava.util.*;importjava.nio.*;publicclass DatagramChannelClientDemo {

? ? // UDP協(xié)議客戶端privateString serverIp = "127.0.0.1";

? ? privateintport = 9975;

? ? // private ServerSocketChannel serverSocketChannel;? ? DatagramChannel channel;

? ? privateCharset charset = Charset.forName("UTF-8");

? ? privateSelector selector =null;

? ? publicDatagramChannelClientDemo()throws IOException {

? ? ? ? try {

? ? ? ? ? ? selector = Selector.open();

? ? ? ? ? ? channel = DatagramChannel.open();

? ? ? ? } catch (Exception e) {

? ? ? ? ? ? selector =null;

? ? ? ? ? ? channel =null;

? ? ? ? ? ? System.out.println("超時(shí)");

? ? ? ? }

? ? ? ? System.out.println("客戶器啟動(dòng)");

? ? }

? ? /* 編碼過程 */public ByteBuffer encode(String str) {

? ? ? ? return charset.encode(str);

? ? }

? ? /* 解碼過程 */public String decode(ByteBuffer bb) {

? ? ? ? return charset.decode(bb).toString();

? ? }

? ? /* 服務(wù)器服務(wù)方法 */publicvoidservice()throws IOException {

? ? ? ? if(channel ==null|| selector ==null)

? ? ? ? ? ? return;

? ? ? ? channel.configureBlocking(false);

? ? ? ? channel.connect(newInetSocketAddress(serverIp, port));// 連接服務(wù)端channel.write(ByteBuffer.wrap(newString("客戶端請求獲取消息").getBytes()));

? ? ? ? channel.register(selector, SelectionKey.OP_READ);

? ? ? ? /** 外循環(huán),已經(jīng)發(fā)生了SelectionKey數(shù)目 */while(selector.select() > 0) {

? ? ? ? ? ? /* 得到已經(jīng)被捕獲了的SelectionKey的集合 */? ? ? ? ? ? Iterator iterator = selector.selectedKeys().iterator();

? ? ? ? ? ? while (iterator.hasNext()) {

? ? ? ? ? ? ? ? SelectionKey key =null;

? ? ? ? ? ? ? ? try {

? ? ? ? ? ? ? ? ? ? key = (SelectionKey) iterator.next();

? ? ? ? ? ? ? ? ? ? iterator.remove();

? ? ? ? ? ? ? ? ? ? if (key.isReadable()) {

? ? ? ? ? ? ? ? ? ? ? ? reveice(key);

? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? ? ? if (key.isWritable()) {

? ? ? ? ? ? ? ? ? ? ? ? // send(key);? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? } catch (IOException e) {

? ? ? ? ? ? ? ? ? ? e.printStackTrace();

? ? ? ? ? ? ? ? ? ? try {

? ? ? ? ? ? ? ? ? ? ? ? if(key !=null) {

? ? ? ? ? ? ? ? ? ? ? ? ? ? key.cancel();

? ? ? ? ? ? ? ? ? ? ? ? ? ? key.channel().close();

? ? ? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? ? ? } catch (ClosedChannelException cex) {

? ? ? ? ? ? ? ? ? ? ? ? e.printStackTrace();

? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? }

? ? ? ? ? ? }

? ? ? ? ? ? /* 內(nèi)循環(huán)完 */? ? ? ? }

? ? ? ? /* 外循環(huán)完 */? ? }

? ? // /*

? ? // * 接收 用read()讀IO

? ? // * */

? ? // synchronized public void reveice2(SelectionKey key) throws IOException {

? ? // if (key == null)

? ? // return;

? ? //// ***用channel.read()獲取消息***////// :接收時(shí)需要考慮字節(jié)長度

? ? // DatagramChannel sc = (DatagramChannel) key.channel();

? ? // String content = "";

? ? //// create buffer with capacity of 48 bytes

? ? // ByteBuffer buf = ByteBuffer.allocate(3);// java里一個(gè)(utf-8)中文3字節(jié),gbk中文占2個(gè)字節(jié)

? ? // int bytesRead = sc.read(buf); //read into buffer.

? ? //// while (bytesRead >0) {

? ? // buf.flip(); //make buffer ready for read

? ? // while(buf.hasRemaining()){

? ? // buf.get(new byte[buf.limit()]); // read 1 byte at a time

? ? // content += new String(buf.array());

? ? // }

? ? // buf.clear(); //make buffer ready for writing

? ? // bytesRead = sc.read(buf);

? ? // }

? ? // System.out.println("接收:" + content);

? ? // }/* 接收 */synchronizedpublicvoidreveice(SelectionKey key)throws IOException {

? ? ? ? String threadName = Thread.currentThread().getName();

? ? ? ? if(key ==null)

? ? ? ? ? ? return;

? ? ? ? try {

? ? ? ? ? ? // ***用channel.receive()獲取消息***//// :接收時(shí)需要考慮字節(jié)長度DatagramChannel sc = (DatagramChannel) key.channel();

? ? ? ? ? ? String content = "";

? ? ? ? ? ? // 第一次接;udp采用數(shù)據(jù)報(bào)模式,發(fā)送多少次,接收多少次ByteBuffer buf = ByteBuffer.allocate(65507);// java里一個(gè)(utf-8)中文3字節(jié),gbk中文占2個(gè)字節(jié)? ? ? ? ? ? buf.clear();

? ? ? ? ? ? SocketAddress address = sc.receive(buf);// read into buffer.String clientAddress = address.toString().replace("/", "").split(":")[0];

? ? ? ? ? ? String clientPost = address.toString().replace("/", "").split(":")[1];

? ? ? ? ? ? System.out.println(threadName + "\t" + address.toString());

? ? ? ? ? ? buf.flip(); // make buffer ready for readwhile (buf.hasRemaining()) {

? ? ? ? ? ? ? ? buf.get(newbyte[buf.limit()]);// read 1 byte at a timebyte[] tmp = buf.array();

? ? ? ? ? ? ? ? content +=new String(tmp);

? ? ? ? ? ? }

? ? ? ? ? ? buf.clear(); // make buffer ready for writing次System.out.println(threadName + "接收:" + content.trim());

? ? ? ? ? ? // 第二次接content = "";

? ? ? ? ? ? ByteBuffer buf2 = ByteBuffer.allocate(65507);// java里一個(gè)(utf-8)中文3字節(jié),gbk中文占2個(gè)字節(jié)? ? ? ? ? ? buf2.clear();

? ? ? ? ? ? SocketAddress address2 = sc.receive(buf2);// read into buffer.buf2.flip();// make buffer ready for readwhile (buf2.hasRemaining()) {

? ? ? ? ? ? ? ? buf2.get(newbyte[buf2.limit()]);// read 1 byte at a timebyte[] tmp = buf2.array();

? ? ? ? ? ? ? ? content +=new String(tmp);

? ? ? ? ? ? }

? ? ? ? ? ? buf2.clear(); // make buffer ready for writing次System.out.println(threadName + "接收2:" + content.trim());

? ? ? ? } catch (PortUnreachableException ex) {

? ? ? ? ? ? System.out.println(threadName + "服務(wù)端端口未找到!");

? ? ? ? }

? ? ? ? send(2);

? ? }

? ? booleanflag =false;

? ? publicvoidsend(int i) {

? ? ? ? if (flag)

? ? ? ? ? ? return;

? ? ? ? try {

? ? ? ? ? ? // channel.write(ByteBuffer.wrap(new

? ? ? ? ? ? // String("客戶端請求獲取消息(第"+i+"次)").getBytes()));

? ? ? ? ? ? // channel.register(selector, SelectionKey.OP_READ );ByteBuffer buf2 = ByteBuffer.allocate(48);

? ? ? ? ? ? buf2.clear();

? ? ? ? ? ? buf2.put(("客戶端請求獲取消息(第" + i + "次)").getBytes());

? ? ? ? ? ? buf2.flip();

? ? ? ? ? ? channel.write(buf2);

? ? ? ? ? ? channel.register(selector, SelectionKey.OP_READ);

? ? ? ? ? ? // int bytesSent = channel.send(buf2, new

? ? ? ? ? ? // InetSocketAddress(serverIp,port)); // 將消息回送給服務(wù)端}catch (IOException e) {

? ? ? ? ? ? e.printStackTrace();

? ? ? ? }

? ? ? ? flag =true;

? ? }

? ? inty = 0;

? ? publicvoid send(SelectionKey key) {

? ? ? ? if(key ==null)

? ? ? ? ? ? return;

? ? ? ? // ByteBuffer buff = (ByteBuffer) key.attachment();DatagramChannel sc = (DatagramChannel) key.channel();

? ? ? ? try {

? ? ? ? ? ? sc.write(ByteBuffer.wrap(newString("aaaa").getBytes()));

? ? ? ? } catch (IOException e1) {

? ? ? ? ? ? e1.printStackTrace();

? ? ? ? }

? ? ? ? System.out.println("send2() " + (++y));

? ? }

? ? /* 發(fā)送文件 */publicvoid sendFile(SelectionKey key) {

? ? ? ? if(key ==null)

? ? ? ? ? ? return;

? ? ? ? ByteBuffer buff = (ByteBuffer) key.attachment();

? ? ? ? SocketChannel sc = (SocketChannel) key.channel();

? ? ? ? String data = decode(buff);

? ? ? ? if(data.indexOf("get") == -1)

? ? ? ? ? ? return;

? ? ? ? String subStr = data.substring(data.indexOf(" "), data.length());

? ? ? ? System.out.println("截取之后的字符串是 " + subStr);

? ? ? ? FileInputStream fileInput =null;

? ? ? ? try {

? ? ? ? ? ? fileInput =new FileInputStream(subStr);

? ? ? ? ? ? FileChannel fileChannel = fileInput.getChannel();

? ? ? ? ? ? fileChannel.transferTo(0, fileChannel.size(), sc);

? ? ? ? ? ? fileChannel.close();

? ? ? ? } catch (IOException e) {

? ? ? ? ? ? e.printStackTrace();

? ? ? ? } finally {

? ? ? ? ? ? try {

? ? ? ? ? ? ? ? fileInput.close();

? ? ? ? ? ? } catch (IOException ex) {

? ? ? ? ? ? ? ? ex.printStackTrace();

? ? ? ? ? ? }

? ? ? ? }

? ? }

? ? publicstaticvoidmain(String[] args)throws IOException {

? ? ? ? newThread(new Runnable() {

? ? ? ? ? ? publicvoid run() {

? ? ? ? ? ? ? ? try {

? ? ? ? ? ? ? ? ? ? new DatagramChannelClientDemo().service();

? ? ? ? ? ? ? ? } catch (IOException e) {

? ? ? ? ? ? ? ? ? ? e.printStackTrace();

? ? ? ? ? ? ? ? }

? ? ? ? ? ? }

? ? ? ? }).start();

? ? ? ? // new Thread(new Runnable() {

? ? ? ? // public void run() {

? ? ? ? // try {

? ? ? ? // new DatagramChannelClientDemo().service();

? ? ? ? // } catch (IOException e) {

? ? ? ? // e.printStackTrace();

? ? ? ? // }

? ? ? ? // }

? ? ? ? // }).start();? ? }

}

Java NIO中的Buffer用于和NIO通道進(jìn)行交互。如你所知,數(shù)據(jù)是從通道讀入緩沖區(qū),從緩沖區(qū)寫入到通道中的。交互圖如下:


緩沖區(qū)本質(zhì)上是一塊可以寫入數(shù)據(jù),然后可以從中讀取數(shù)據(jù)的內(nèi)存。這塊內(nèi)存被包裝成NIO Buffer對象,并提供了一組方法,用來方便的訪問該塊內(nèi)存。緩沖區(qū)實(shí)際上是一個(gè)容器對象,更直接的說,其實(shí)就是一個(gè)數(shù)組,在NIO庫中,所有數(shù)據(jù)都是用緩沖區(qū)處理的。在讀取數(shù)據(jù)時(shí),它是直接讀到緩沖區(qū)中的; 在寫入數(shù)據(jù)時(shí),它也是寫入到緩沖區(qū)中的;任何時(shí)候訪問 NIO 中的數(shù)據(jù),都是將它放到緩沖區(qū)中。而在面向流I/O系統(tǒng)中,所有數(shù)據(jù)都是直接寫入或者直接將數(shù)據(jù)讀取到Stream對象中。

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

Buffer的基本用法

Buffer的基本用法

使用Buffer讀寫數(shù)據(jù)一般遵循以下四個(gè)步驟:

寫入數(shù)據(jù)到Buffer

調(diào)用flip()方法

從Buffer中讀取數(shù)據(jù)

調(diào)用clear()方法或者compact()方法

當(dāng)向buffer寫入數(shù)據(jù)時(shí),buffer會(huì)記錄下寫了多少數(shù)據(jù)。一旦要讀取數(shù)據(jù),需要通過flip()方法將Buffer從寫模式切換到讀模式。在讀模式下,可以讀取之前寫入到buffer的所有數(shù)據(jù)。

一旦讀完了所有的數(shù)據(jù),就需要清空緩沖區(qū),讓它可以再次被寫入。有兩種方式能清空緩沖區(qū):調(diào)用clear()或compact()方法。clear()方法會(huì)清空整個(gè)緩沖區(qū)。compact()方法只會(huì)清除已經(jīng)讀過的數(shù)據(jù)。任何未讀的數(shù)據(jù)都被移到緩沖區(qū)的起始處,新寫入的數(shù)據(jù)將放到緩沖區(qū)未讀數(shù)據(jù)的后面。

下面是一個(gè)使用Buffer的例子:

01RandomAccessFile aFile =?newRandomAccessFile("data/nio-data.txt",?"rw");

02FileChannel inChannel = aFile.getChannel();

03?

04//create buffer with capacity of 48 bytes

05ByteBuffer buf = ByteBuffer.allocate(48);

06?

07intbytesRead = inChannel.read(buf);?//read into buffer.

08while(bytesRead != -1) {

09?

10??buf.flip();??//make buffer ready for read

11?

12??while(buf.hasRemaining()){

13??????System.out.print((char) buf.get());?// read 1 byte at a time

14??}

15?

16??buf.clear();?//make buffer ready for writing

17??bytesRead = inChannel.read(buf);

18}

19aFile.close();? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? }

示例2:

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

package com.dxz.nio;import java.nio.IntBuffer;publicclass TestIntBuffer {

? ? publicstaticvoid main(String[] args) {?

? ? ? ? // 分配新的int緩沖區(qū),參數(shù)為緩沖區(qū)容量?

? ? ? ? // 新緩沖區(qū)的當(dāng)前位置將為零,其界限(限制位置)將為其容量。它將具有一個(gè)底層實(shí)現(xiàn)數(shù)組,其數(shù)組偏移量將為零。? IntBuffer buffer = IntBuffer.allocate(8);?

? ? ? ? for(inti = 0; i < buffer.capacity(); ++i) {?

? ? ? ? ? ? intj = 2 * (i + 1);

? ? ? ? ? ? // 將給定整數(shù)寫入此緩沖區(qū)的當(dāng)前位置,當(dāng)前位置遞增? ? ? ? ? ? ? buffer.put(j);?

? ? ? ? }?

? ? ? ? // 重設(shè)此緩沖區(qū),將限制設(shè)置為當(dāng)前位置,然后將當(dāng)前位置設(shè)置為0? ? ? ? ? buffer.flip();?

? ? ? ? // 查看在當(dāng)前位置和限制位置之間是否有元素? while (buffer.hasRemaining()) {?

? ? ? ? ? ? // 讀取此緩沖區(qū)當(dāng)前位置的整數(shù),然后當(dāng)前位置遞增? intj = buffer.get();?

? ? ? ? ? ? System.out.print(j + "? ");?

? ? ? ? }?

? ? }?

}?

結(jié)果:

2 4 6 8 10 12 14 16

Buffer的capacity,position和limit

緩沖區(qū)本質(zhì)上是一塊可以寫入數(shù)據(jù),然后可以從中讀取數(shù)據(jù)的內(nèi)存。這塊內(nèi)存被包裝成NIO Buffer對象,并提供了一組方法,用來方便的訪問該塊內(nèi)存。

為了理解Buffer的工作原理,需要熟悉它的三個(gè)屬性:

capacity

position

limit

position和limit的含義取決于Buffer處在讀模式還是寫模式。不管Buffer處在什么模式,capacity的含義總是一樣的。

這里有一個(gè)關(guān)于capacity,position和limit在讀寫模式中的說明,詳細(xì)的解釋在插圖后面。

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)

Buffer的類型

Java NIO 有以下Buffer類型

ByteBuffer

MappedByteBuffer

CharBuffer

DoubleBuffer

FloatBuffer

IntBuffer

LongBuffer

ShortBuffer

p<>

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

MappedByteBuffer 有些特別,在涉及它的專門章節(jié)中再講。

Buffer的分配

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

1ByteBuffer buf = ByteBuffer.allocate(48);

這是分配一個(gè)可存儲(chǔ)1024個(gè)字符的CharBuffer:

1CharBuffer buf = CharBuffer.allocate(1024);

向Buffer中寫數(shù)據(jù)

寫數(shù)據(jù)到Buffer有兩種方式:

從Channel寫到Buffer。

通過Buffer的put()方法寫到Buffer里。

從Channel寫到Buffer的例子

1intbytesRead = inChannel.read(buf);?//read into buffer.

通過put方法寫B(tài)uffer的例子:

1buf.put(127);

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

flip()方法

flip英?[fl?p]美?[fl?p]?及物動(dòng)詞 輕彈,輕擊; 按(開關(guān)); 快速翻轉(zhuǎn); 急揮

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ù)。

從Buffer讀取數(shù)據(jù)到Channel的例子:

//read from buffer into channel.intbytesWritten = inChannel.write(buf);


使用get()方法從Buffer中讀取數(shù)據(jù)的例子

byteaByte = 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。例如:

1buffer.mark();

2?

3//call buffer.get() a couple of times, e.g. during parsing.

4?

5buffer.reset();??//set position back to mark.

equals()與compareTo()方法

可以使用equals()和compareTo()方法兩個(gè)Buffer。

equals()

當(dāng)滿足下列條件時(shí),表示兩個(gè)Buffer相等:

有相同的類型(byte、char、int等)。

Buffer中剩余的byte、char等的個(gè)數(shù)相等。

Buffer中所有剩余的byte、char等都相同。

如你所見,equals只是比較Buffer的一部分,不是每一個(gè)在它里面的元素都比較。實(shí)際上,它只比較Buffer中的剩余元素。

compareTo()方法

compareTo()方法比較兩個(gè)Buffer的剩余元素(byte、char等), 如果滿足下列條件,則認(rèn)為一個(gè)Buffer“小于”另一個(gè)Buffer:

第一個(gè)不相等的元素小于另一個(gè)Buffer中對應(yīng)的元素 。

所有元素都相等,但第一個(gè)Buffer比另一個(gè)先耗盡(第一個(gè)Buffer的元素個(gè)數(shù)比另一個(gè)少)。


緩沖區(qū)分片

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

package com.dxz.nio;import java.nio.ByteBuffer;publicclass BufferDemo1 {

? ? staticpublicvoidmain(String args[])throws Exception {

? ? ? ? ByteBuffer buffer = ByteBuffer.allocate(10);

? ? ? ? // 緩沖區(qū)中的數(shù)據(jù)0-9for(inti = 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(inti = 0; i < slice.capacity(); ++i) {

? ? ? ? ? ? byteb = 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());

? ? ? ? }

? ? }

}



結(jié)果:

0

1

2

30

40

50

60

7

8

9

只讀緩沖區(qū)

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

package com.dxz.nio;import java.nio.ByteBuffer;publicclass BufferDemo2 {

? ? staticpublicvoidmain(String args[])throws Exception {

? ? ? ? ByteBuffer buffer = ByteBuffer.allocate(10);

? ? ? ? // 緩沖區(qū)中的數(shù)據(jù)0-9for(inti = 0; i < buffer.capacity(); ++i) {

? ? ? ? ? ? buffer.put((byte) i);

? ? ? ? }

? ? ? ? // 創(chuàng)建只讀緩沖區(qū)ByteBuffer readonly = buffer.asReadOnlyBuffer();

? ? ? ? // 改變原緩沖區(qū)的內(nèi)容for(inti = 0; i < buffer.capacity(); ++i) {

? ? ? ? ? ? byteb = 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());

? ? ? ? }

? ? }

}

結(jié)果:

0

10

20

30

40

50

60

70

80

90


如果嘗試修改只讀緩沖區(qū)的內(nèi)容,則會(huì)報(bào)ReadOnlyBufferException異常。只讀緩沖區(qū)對于保護(hù)數(shù)據(jù)很有用。在將緩沖區(qū)傳遞給某個(gè) 對象的方法時(shí),無法知道這個(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ī)將盡最大努 力直接對它執(zhí)行本機(jī)I/O操作。也就是說,它會(huì)在每一次調(diào)用底層操作系統(tǒng)的本機(jī)I/O操作之前(或之后),嘗試避免將緩沖區(qū)的內(nèi)容拷貝到一個(gè)中間緩沖區(qū)中 或者從一個(gè)中間緩沖區(qū)中拷貝數(shù)據(jù)。要分配直接緩沖區(qū),需要調(diào)用allocateDirect()方法,而不是allocate()方法,使用方式與普通緩沖區(qū)并無區(qū)別,如下面的拷貝文件示例:

package com.dxz.nio;import java.io.FileInputStream;import java.io.FileOutputStream;import java.nio.ByteBuffer;importjava.nio.channels.*;publicclass BufferDemo3 {

? ? staticpublicvoidmain(String args[])throws Exception {

? ? ? ? String infile = "e:\\logs\\test.txt";

? ? ? ? FileInputStream fin =new FileInputStream(infile);

? ? ? ? FileChannel fcin = fin.getChannel();

? ? ? ? String outfile = String.format("e:\\logs\\testcopy.txt");

? ? ? ? FileOutputStream fout =new FileOutputStream(outfile);

? ? ? ? FileChannel fcout = fout.getChannel();

? ? ? ? // 使用allocateDirect,而不是allocateByteBuffer buffer = ByteBuffer.allocateDirect(1024);

? ? ? ? while(true) {

? ? ? ? ? ? buffer.clear();

? ? ? ? ? ? intr = 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是通過使文件中的數(shù)據(jù)出現(xiàn)為 內(nèi)存數(shù)組的內(nèi)容來完成的,這其初聽起來似乎不過就是將整個(gè)文件讀到內(nèi)存中,但是事實(shí)上并不是這樣。一般來說,只有文件中實(shí)際讀取或者寫入的部分才會(huì)映射到內(nèi)存中。如下面的示例代碼:

package com.dxz.nio;import java.io.RandomAccessFile;import java.nio.MappedByteBuffer;importjava.nio.channels.*;publicclass BufferDemo4 {

? ? staticprivatefinalintstart = 0;

? ? staticprivatefinalintsize = 1024;

? ? staticpublicvoidmain(String args[])throws Exception {

? ? ? ? RandomAccessFile raf =newRandomAccessFile("e:\\logs\\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();

? ? }

}

為什么某些人會(huì)一直比你優(yōu)秀,是因?yàn)樗旧砭秃軆?yōu)秀還一直在持續(xù)努力變得更優(yōu)秀,而你是不是還在滿足于現(xiàn)狀內(nèi)心在竊喜!?

關(guān)注我,

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

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