一、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、阻塞模式
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讀寫數(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)注我,