NIO的概述
NIO是New I/O的簡稱,與舊式基於流的I/O相對,從名字上來看,它表示新的一套I/O標準。它是從JDK1.4中被納入到JDK中的。
與舊式的IO流相比,NIO是基於Block的,它以塊爲單位來處理數據,最爲重要的兩個組件是緩衝區Buffer和通道Channel。緩衝區是一塊連續的內存塊,是NIO讀寫數據的載體;通道表示緩衝數據的源頭和目的地,它用於向緩衝區讀取或者寫入數據,是訪問緩衝區的接口。
Buffer的基本原理
Buffer中最重要的3個參數:位置(position)、容量(capacity)、上限(limit)。他們3者的含義如下
位置(position): 表示當前緩衝區的位置,從position位置之後開始讀寫數據。
容量(capacity): 表示緩衝區的最大容量
上限(limit): 表示緩衝區的實際上限,它總是小於或等於容量
緩衝區的容量(capacity)是不變的,而位置(position)和上限(limit)和以根據實際需要而變化。也就是說,可以通過改變當前位置和上限來操作緩衝區內任意位置的數據。
Buffer的常用方法
NIO提供一系列方法來操作Buffer的位置(position)和上限(limit),以及向緩衝區讀寫數據。
put() //向緩衝區position位置添加數據。並且position往後移動,不能超過limit上限。
get() //讀取當前position位置的數據。並且position往後移動,不能超過limit上限。
flip() //將limit置位爲當前position位置,再講position設置爲0
rewind() //僅將當前position位置設置爲0
remaining //獲取緩衝區中當前position位置和limit上限之間的元素數(有效的元素數)
hasRemaining() //判斷當前緩衝區是否存在有效的元素數
mark() //在當前position位置打一個標記
reset() //將當前position位置恢復到mark標記的位置。
duplicate() //複製緩衝區
創建緩衝區
//創建一個容量爲10的緩衝區
ByteBuffer byteBuffer1=ByteBuffer.allocate(10);
//創建一個包裹數據的緩衝區
ByteBuffer byteBuffer2 = ByteBuffer.wrap("abcde".getBytes());
獲取/設置緩衝區參數
//創建一個容量爲10的緩衝區
ByteBuffer byteBuffer=ByteBuffer.allocate(10);
System.out.println("位置:"+byteBuffer.position()); //0
System.out.println("上限:"+byteBuffer.limit()); //10
System.out.println("容量:"+byteBuffer.capacity());//10
添加數據到緩衝區
//創建一個容量爲10的緩衝區
ByteBuffer byteBuffer=ByteBuffer.allocate(10);
//添加數據到緩衝區
byteBuffer.put("abcde".getBytes());
System.out.println("position位置:"+byteBuffer.position()); //5
System.out.println("limit上限:"+byteBuffer.limit()); //10
System.out.println("capacity容量:"+byteBuffer.capacity()); //10
rewind重置緩衝區
rewind函數將position置爲0位置,並清除標記。
//創建一個容量爲10的緩衝區
ByteBuffer byteBuffer=ByteBuffer.allocate(10);
//添加數據到緩衝區
byteBuffer.put("abcde".getBytes());
System.out.println("position位置:"+byteBuffer.position()); //5
System.out.println("limit上限:"+byteBuffer.limit()); //10
System.out.println("capacity容量:"+byteBuffer.capacity()); //10
System.out.println("------------");
//重置緩衝區
byteBuffer.rewind();
System.out.println("position位置:"+byteBuffer.position()); //0
System.out.println("limit上限:"+byteBuffer.limit()); //10
System.out.println("capacity容量:"+byteBuffer.capacity());//10
flip重置緩衝區
flip函數現將limit設置爲position位置,再將position置爲0位置,並清除mar標記。
//創建一個容量爲10的緩衝區
ByteBuffer byteBuffer=ByteBuffer.allocate(10);
//添加數據到緩衝區
byteBuffer.put("abcde".getBytes());
System.out.println("position位置:"+byteBuffer.position()); //5
System.out.println("limit上限:"+byteBuffer.limit()); //10
System.out.println("capacity容量:"+byteBuffer.capacity()); //10
System.out.println("------------");
//重置緩衝區
byteBuffer.flip();
System.out.println("position位置:"+byteBuffer.position()); //0
System.out.println("limit上限:"+byteBuffer.limit()); //5
System.out.println("capacity容量:"+byteBuffer.capacity());//10
clear清空緩衝區
clear方法也將position置爲0,同時將limit置爲capacity的大小,並清除mark標記。
//創建一個容量爲10的緩衝區
ByteBuffer byteBuffer=ByteBuffer.allocate(10);
//設置上限爲5
byteBuffer.limit(5);
//添加數據到緩衝區
byteBuffer.put("abcde".getBytes());
System.out.println("position位置:"+byteBuffer.position()); //5
System.out.println("limit上限:"+byteBuffer.limit()); //5
System.out.println("capacity容量:"+byteBuffer.capacity()); //10
System.out.println("------------");
//重置緩衝區
byteBuffer.clear();
System.out.println("position位置:"+byteBuffer.position()); //0
System.out.println("limit上限:"+byteBuffer.limit()); //10
System.out.println("capacity容量:"+byteBuffer.capacity());//10
標記和恢復
ByteBuffer buffer = ByteBuffer.allocate(10);
//添加數據到緩衝區
buffer.put("abcde".getBytes());
//打一個標記
buffer.mark();
System.out.println("標記位置:"+buffer.position()); //5
//再添加5個字節數據到緩衝區
buffer.put("fijkl".getBytes());
System.out.println("當前位置:"+buffer.position()); //10
//將position恢復到mark標記位置
buffer.reset();
System.out.println("恢復標記位置:"+buffer.position());//5
FileChannel通道
FileChannel是用於操作文件的通道,可以用於讀取和寫入文件中的數據。
//創建讀取文件通道
FileChannel fisChannel = new FileInputStream("day05/src/a.txt").getChannel();
//創建寫入文件的通道
FileChannel fosChannel = new FileOutputStream("day05/src/b.txt").getChannel();
//創建緩衝區
ByteBuffer buffer = ByteBuffer.allocate(2);
while (fisChannel.read(buffer)!=-1){
System.out.println("position:"+buffer.position()); //0
System.out.println("limit:"+buffer.limit());//2
//重置緩衝區(爲輸出buffer數據做準備)
buffer.flip();
fosChannel.write(buffer);
//重置緩衝區(爲輸入buffer數據做準備)
buffer.clear();
}
//關閉通道
fisChannel.close();
fosChannel.close();
SocketChannel通道
下面代碼使用SocketChannel通道上傳文件到服務器
public class Client {
public static void main(String[] args) throws IOException {
//創建通道
SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 10000));
//創建緩衝區
ByteBuffer buffer = ByteBuffer.allocate(1024);
//讀取本地文件數據到緩衝區
FileChannel fisChannel = new FileInputStream("day05/src/a.txt").getChannel();
while (fisChannel.read(buffer)!=-1){
buffer.flip(); //爲寫入數據做準備
socketChannel.write(buffer);
buffer.clear(); //爲讀取數據做準備
}
//關閉本地通道
fisChannel.close();
//socketChannel.shutdownOutput();
//讀取服務端回寫的數據
buffer.clear();
int len = socketChannel.read(buffer);
System.out.println(new String(buffer.array(), 0, len));
//關閉socket通道
socketChannel.close();
}
}
ServerSocketChannel通道
下面代碼使用ServerSocketChannel通道接收文件並保存到服務器
public class Server {
public static void main(String[] args) throws IOException {
//1.創建ServerSocketChannel通道
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//2.綁定端口號
serverSocketChannel.bind(new InetSocketAddress(10000));
//3.設置非阻塞
serverSocketChannel.configureBlocking(false);
System.out.println("服務器已開啓");
while (true) {
//4.獲取客戶端通道,如果有客戶端連接返回客戶端通道,否則返回null
SocketChannel socketChannel = serverSocketChannel.accept();
if(socketChannel!=null){
socketChannel.configureBlocking(false);
//創建本地通道,用於往文件中寫數據
UUID uuid = UUID.randomUUID();
FileChannel fosChannel=new FileOutputStream("day05/src/"+uuid+".txt").getChannel();
ByteBuffer buffer=ByteBuffer.allocate(1024);
while (socketChannel.read(buffer)>0){
buffer.flip(); //準備把緩衝區數據輸出
fosChannel.write(buffer);
buffer.clear();//準備讀取數據到緩衝區
}
fosChannel.close();
//回寫數據到客戶端
ByteBuffer resultBuffer=ByteBuffer.wrap("上傳文件成功".getBytes());
socketChannel.write(resultBuffer);
//關閉客戶端通道
socketChannel.close();
}
}
}
}
NIO Selector選擇器
Selector 一般稱 爲選擇器 ,當然你也可以翻譯爲 多路複用器 。它是Java NIO核心組件中的一個,用於檢查一個或多個NIO Channel(通道)的狀態是否處於可讀、可寫。如此可以實現單線程管理多個channels,也就是可以管理多個網絡鏈接。
使用Selector的服務器模板代碼
有了模板代碼我們在編寫程序時,大多數時間都是在模板代碼中添加相應的業務代碼
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.socket().bind(new InetSocketAddress("localhost", 8080));
ssc.configureBlocking(false);
Selector selector = Selector.open();
ssc.register(selector, SelectionKey.OP_ACCEPT);
while(true) {
int readyNum = selector.select();
if (readyNum == 0) {
continue;
}
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> it = selectedKeys.iterator();
while(it.hasNext()) {
SelectionKey key = it.next();
if(key.isAcceptable()) {
// 接受連接
} else if (key.isReadable()) {
// 通道可讀
} else if (key.isWritable()) {
// 通道可寫
}
it.remove();
}
}