NIO是非阻塞的IO,Java NIO由一下幾個核心部分組成:Buffers、Channels、Selectors。
Java NIO中有很多類和組件,但是Buffers、Channels、Selectors構(gòu)成了核心的API。其他組件如Pipe和FileLock,只不過是與其他三個核心組件共同使用的工具類。
- Channel:基本上所有的IO在NIO中都從一個
Channel開始,Channel有點像流。數(shù)據(jù)可以從Channel讀到Buffer中,也可以從Buffer寫到Channel中。Channel和Buffer有好多類型,Channel主要有:FileChannel、DataGramChannel、SocketChannel、ServerSocketChannel。涵蓋了UDP和TCP網(wǎng)絡(luò)的IO以及文件IO。
-
Buffer:NIO主要的Buffer有:
ByteBuffer、CharBuffer、DoubleBuffer、FloatBuffer、IntBuffer、LongBuffer、ShortBuffer,這些Buffer涵蓋了你能通過IO發(fā)送的基本數(shù)據(jù)類型。 -
Selector:允許單線程處理多個
Channel。如果你的應(yīng)用打開了多個連接(通道),但每個連接的流量都很低,使用Selector就會很方便。例如一個聊天服務(wù)器中。要使用Selector,得先向Selector注冊Channel然后調(diào)用它的select()方法。這個方法會一直堵塞知道某個注冊的通道有事件就緒。一旦這個方法返回線程就可以處理這些事件,事件的例子有如新連接進來,數(shù)據(jù)接收等。
緩沖區(qū)&Buffer
在整個Java新IO中,所有的操作都是以緩沖區(qū)進行的,使用緩沖區(qū),則操作的性能將是最高的。
在Buffer中存在一系列的狀態(tài)變量,隨著寫入或讀取都有可能被改變,在緩沖區(qū)可以使用三個值表示緩沖區(qū)狀態(tài)
- position 位置
- limit 界限
- capacity 容量
ByteBuffer的使用
- 創(chuàng)建ByteBuffer
(1)使用allocate()靜態(tài)方法
ByteBuffer buffer=ByteBuffer.allocate(256);
以上方法將創(chuàng)建一個容量為256字節(jié)的ByteBuffer,如果發(fā)現(xiàn)創(chuàng)建的緩沖區(qū)容量太小,唯一的選擇就是重新創(chuàng)建一個大小合適的緩沖區(qū).
(2)通過包裝一個已有的數(shù)組來創(chuàng)建
如下,通過包裝的方法創(chuàng)建的緩沖區(qū)保留了被包裝數(shù)組內(nèi)保存的數(shù)據(jù).
ByteBuffer buffer=ByteBuffer.wrap(byteArray);
如果要將一個字符串存入ByteBuffer,可以如下操作:
String sendString="你好,服務(wù)器. "; ByteBuffer sendBuffer=ByteBuffer.wrap(sendString.getBytes("UTF-16"));
- 緩沖區(qū)
- buffer.flip();
這個方法用來將緩沖區(qū)準(zhǔn)備為數(shù)據(jù)傳出狀態(tài),執(zhí)行以上方法后,輸出通道會從數(shù)據(jù)的開頭而不是末尾開始.回繞保持緩沖區(qū)中的數(shù)據(jù)不變,只是準(zhǔn)備寫入而不是讀取.
- buffer.clear();
這個方法實際上也不會改變緩沖區(qū)的數(shù)據(jù),而只是簡單的重置了緩沖區(qū)的主要索引值.不必為了每次讀寫都創(chuàng)建新的緩沖區(qū),那樣做會降低性能.相反,要重用現(xiàn)在的緩沖區(qū),在再次讀取之前要清除緩沖區(qū).
ByteBuffer轉(zhuǎn)為其他的Buffer,如:CharBuffer、DoubleBuffer、IntBuffer、LongBuffer、ShortBuffer,都有對應(yīng)的asXXXBuffer()方法。
- 創(chuàng)建子緩沖區(qū) buf.slice()
子緩沖區(qū)可以修改數(shù)據(jù)
import java.nio.IntBuffer ;
public class IntBufferDemo02{
public static void main(String args[]){
IntBuffer buf = IntBuffer.allocate(10) ; // 準(zhǔn)備出10個大小的緩沖區(qū)
IntBuffer sub = null ; // 定義子緩沖區(qū)
for(int i=0;i<10;i++){
buf.put(2 * i + 1) ; // 在主緩沖區(qū)中加入10個奇數(shù)
}
// 需要通過slice() 創(chuàng)建子緩沖區(qū)
buf.position(2) ;
buf.limit(6) ;
sub = buf.slice() ;
for(int i=0;i<sub.capacity();i++){
int temp = sub.get(i) ;
sub.put(temp-1) ;
}
buf.flip() ; // 重設(shè)緩沖區(qū)
buf.limit(buf.capacity()) ;
System.out.print("主緩沖區(qū)中的內(nèi)容:") ;
while(buf.hasRemaining()){
int x = buf.get() ;
System.out.print(x + "、") ;
}
}
}
- 創(chuàng)建只讀緩沖區(qū) buf.asReadOnlyBuffer()
import java.nio.IntBuffer ;
public class IntBufferDemo03{
public static void main(String args[]){
IntBuffer buf = IntBuffer.allocate(10) ; // 準(zhǔn)備出10個大小的緩沖區(qū)
IntBuffer read = null ; // 定義子緩沖區(qū)
for(int i=0;i<10;i++){
buf.put(2 * i + 1) ; // 在主緩沖區(qū)中加入10個奇數(shù)
}
read = buf.asReadOnlyBuffer() ;// 創(chuàng)建只讀緩沖區(qū)
read.flip() ; // 重設(shè)緩沖區(qū)
System.out.print("主緩沖區(qū)中的內(nèi)容:") ;
while(read.hasRemaining()){
int x = read.get() ;
System.out.print(x + "、") ;
}
read.put(30) ; // 修改,錯誤
}
}
- 創(chuàng)建直接緩沖區(qū)
public static ByteBuffer allocateDirect(int capacity)
只能提高一些盡可能的性能。
import java.nio.ByteBuffer ;
public class ByteBufferDemo01{
public static void main(String args[]){
ByteBuffer buf = ByteBuffer.allocateDirect(10) ; // 準(zhǔn)備出10個大小的緩沖區(qū)
byte temp[] = {1,3,5,7,9} ; // 設(shè)置內(nèi)容
buf.put(temp) ; // 設(shè)置一組內(nèi)容
buf.flip() ;
System.out.print("主緩沖區(qū)中的內(nèi)容:") ;
while(buf.hasRemaining()){
int x = buf.get() ;
System.out.print(x + "、") ;
}
}
}
通道
- 可讀、可寫。程序不會直接操作通道,所有的內(nèi)容讀到或者寫入緩沖區(qū),再通過緩沖區(qū)中取得或?qū)懭搿?/li>
- 傳統(tǒng)的流操作分為輸入或輸出流,而通道本身是雙向操作的,可以完成輸入也可以完成輸出。
讀入方式
- RandomAccessFile:較慢
- FileInputStream:較慢
- 緩存讀?。核俣容^快
- 內(nèi)存映射MapByteBuffer:最快!
FileChannel的三種內(nèi)存映射模式
| NO. | 常量 | 類型 | 描述 |
|---|---|---|---|
| 1 | public static final FileChannel.MapMode.READ_ONLY | 常量 | 只讀映射模式 |
| 2 | public static final FileChannel.MapMode.READ_WRITE | 常量 | 讀取/寫入映射模式 |
| 3 | public static final FileChannel.MapMode.PRIVATE | 常量 | 專用(寫入時拷貝)映射模式 |
import java.nio.ByteBuffer ;
import java.nio.MappedByteBuffer ;
import java.nio.channels.FileChannel ;
import java.io.File ;
import java.io.FileOutputStream ;
import java.io.FileInputStream ;
public class FileChannelDemo03{
public static void main(String args[]) throws Exception{
File file = new File("d:" + File.separator + "mldn.txt") ;
FileInputStream input = null ;
input = new FileInputStream(file) ;
FileChannel fin = null ; // 定義輸入的通道
fin = input.getChannel() ; // 得到輸入的通道
MappedByteBuffer mbb = null ;
mbb = fin.map(FileChannel.MapMode.READ_ONLY,0,file.length()) ;
byte data[] = new byte[(int)file.length()] ; // 開辟空間接收內(nèi)容
int foot = 0 ;
while(mbb.hasRemaining()){
data[foot++] = mbb.get() ; // 讀取數(shù)據(jù)
}
System.out.println(new String(data)) ; // 輸出內(nèi)容
fin.close() ;
input.close() ;
}
}
內(nèi)存映射在讀取時速度快,但是如果在使用以上操作代碼的時候,執(zhí)行的是寫入操作則有可能非常危險,因為僅僅是改變數(shù)組中單個元素這種簡單的操作,就可能直接修改磁盤上的文件,因為修改數(shù)據(jù)與將數(shù)據(jù)保存在磁盤上是一樣的。
文件鎖FileLock
當(dāng)一個線程將文件鎖定之后,其它線程無法操作此文件,要想進行文件鎖定操作,需要使用FileLock類完成,此類的對象需要依靠FileChannel進行實例化操作。

鎖定方式
共享鎖:允許多個線程進行文件的讀/寫操作
-
獨占鎖:只允許一個線程進行文件的讀/寫操作
字符集 Charset
整個NIO中,對于不同平臺的編碼操作,java都可以進行自動適應(yīng),因為可以使用字符集進行字符編碼的轉(zhuǎn)換。
在java中,所有信息都是以UNICODE進行編碼,計算機中存在多種編碼,NIO中提供了charset類來處理編碼問題,包括了創(chuàng)建編碼器(
CharsetEndoder)和創(chuàng)建解碼器(CharsetDecoder)的操作。

Charset latin1 = Charset.forName("ISO-8859-1") ; // 只能表示的英文字符
CharsetEncoder encoder = latin1.newEncoder() ; // 得到編碼器
CharsetDecoder decoder = latin1.newDecoder() ; // 得到解碼器
// 通過CharBuffer類中的
CharBuffer cb = CharBuffer.wrap("啊哈哈哈哈") ;
ByteBuffer buf = encoder.encode(cb) ; // 進行編碼操作
System.out.println(decoder.decode(buf)) ; // 進行解碼操作
Selector
解決服務(wù)器端的通訊性能。
- 使用
Selector可以構(gòu)建一個非阻塞的網(wǎng)絡(luò)服務(wù) - 在NIO中實現(xiàn)網(wǎng)絡(luò)程序需要依靠
ServerSocketChannel類與SocketChannel類
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.util.Set;
import java.util.Iterator;
import java.util.Date;
import java.nio.channels.ServerSocketChannel;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.nio.channels.Selector;
import java.nio.channels.SelectionKey;
public class DateServer {
public static void main(String args[]) throws Exception {
int ports[] = { 8000, 8001, 8002, 8003, 8005, 8006 }; // 表示五個監(jiān)聽端口
Selector selector = Selector.open(); // 通過open()方法找到Selector
for (int i = 0; i < ports.length; i++) {
ServerSocketChannel initSer = null;
initSer = ServerSocketChannel.open(); // 打開服務(wù)器的通道
initSer.configureBlocking(false); // 服務(wù)器配置為非阻塞
ServerSocket initSock = initSer.socket();
InetSocketAddress address = null;
address = new InetSocketAddress(ports[i]); // 實例化綁定地址
initSock.bind(address); // 進行服務(wù)的綁定
initSer.register(selector, SelectionKey.OP_ACCEPT); // 等待連接
System.out.println("服務(wù)器運行,在" + ports[i] + "端口監(jiān)聽。");
}
// 要接收全部生成的key,并通過連接進行判斷是否獲取客戶端的輸出
int keysAdd = 0;
while ((keysAdd = selector.select()) > 0) { // 選擇一組鍵,并且相應(yīng)的通道已經(jīng)準(zhǔn)備就緒
Set<SelectionKey> selectedKeys = selector.selectedKeys();// 取出全部生成的key
Iterator<SelectionKey> iter = selectedKeys.iterator();
while (iter.hasNext()) {
SelectionKey key = iter.next(); // 取出每一個key
if (key.isAcceptable()) {
ServerSocketChannel server = (ServerSocketChannel) key.channel();
SocketChannel client = server.accept(); // 接收新連接
client.configureBlocking(false);// 配置為非阻塞
ByteBuffer outBuf = ByteBuffer.allocateDirect(1024); //
outBuf.put(("當(dāng)前的時間為:" + new Date()).getBytes()); // 向緩沖區(qū)中設(shè)置內(nèi)容
outBuf.flip();
client.write(outBuf); // 輸出內(nèi)容
client.close(); // 關(guān)閉
}
}
selectedKeys.clear(); // 清楚全部的key
}
}
}
