Java NIO全稱java non-blocking IO,最早出現於jdk1.4版本中,提供了一種新的IO實現,在一定程度上可以用於替代傳統的IO。
Java NIO主要包含以下三個部分:
1.Channel
2.Buffer
3.Selector
Java NIO的所有功能實現都是基於這三個部分,接下來我分別詳細的介紹一下這幾個接口。
一、Channle
Channle類似於傳統IO中的Stream,數據可以通過Channle進行傳輸,但是也存在一些不同:
1.Channle中數據的傳輸是雙向的,即可以向Channle中寫數據,也可以從中讀取數據,而IO中的Stream是單向傳輸的。
2.可以同時向Channle中寫數據和讀取數據。
常用的Channle的實現:
1.FileChannle:通常使用FileChannle來對文件進行讀操作和寫操作,在FileInputStream和FileOutputStream類中就提供了getChannel()方法來獲取FileChannle對象。
2.SocketChannel:用於TCP網絡傳輸中的數據讀寫。
3.ServerSocketChannel:用於TCP網絡傳輸中,服務器端監聽客戶端請求,並獲取到一個SocketChannle對象。
4.DatagramChannel:用於UDP網絡傳輸中的數據讀寫。
二、Buffer
一個buffer就相當於一個內存塊,我們可以向buffer中寫入數據,也可以從buffer中讀取數據。
Buffer是一個抽象類,下面我貼出了對應的源碼:
public abstract class Buffer {
static final int SPLITERATOR_CHARACTERISTICS =
Spliterator.SIZED | Spliterator.SUBSIZED | Spliterator.ORDERED;
// Invariants: mark <= position <= limit <= capacity
private int mark = -1;
private int position = 0;
private int limit;
private int capacity;
long address;
Buffer(int mark, int pos, int lim, int cap) {
if (cap < 0)
throw new IllegalArgumentException("Negative capacity: " + cap);
this.capacity = cap;
limit(lim);
position(pos);
if (mark >= 0) {
if (mark > pos)
throw new IllegalArgumentException("mark > position: ("
+ mark + " > " + pos + ")");
this.mark = mark;
}
}
public final int capacity() {
return capacity;
}
public final int position() {
return position;
}
public final Buffer position(int newPosition) {
if ((newPosition > limit) || (newPosition < 0))
throw new IllegalArgumentException();
position = newPosition;
if (mark > position) mark = -1;
return this;
}
public final int limit() {
return limit;
}
public final Buffer limit(int newLimit) {
if ((newLimit > capacity) || (newLimit < 0))
throw new IllegalArgumentException();
limit = newLimit;
if (position > limit) position = limit;
if (mark > limit) mark = -1;
return this;
}
public final Buffer mark() {
mark = position;
return this;
}
public final Buffer reset() {
int m = mark;
if (m < 0)
throw new InvalidMarkException();
position = m;
return this;
}
public final Buffer clear() {
position = 0;
limit = capacity;
mark = -1;
return this;
}
public final Buffer flip() {
limit = position;
position = 0;
mark = -1;
return this;
}
public final Buffer rewind() {
position = 0;
mark = -1;
return this;
}
public final int remaining() {
return limit - position;
}
public final boolean hasRemaining() {
return position < limit;
}
public abstract boolean isReadOnly();
public abstract boolean hasArray();
public abstract Object array();
public abstract int arrayOffset();
public abstract boolean isDirect();
final int nextGetIndex() {
if (position >= limit)
throw new BufferUnderflowException();
return position++;
}
final int nextGetIndex(int nb) {
if (limit - position < nb)
throw new BufferUnderflowException();
int p = position;
position += nb;
return p;
}
final int nextPutIndex() {
if (position >= limit)
throw new BufferOverflowException();
return position++;
}
final int nextPutIndex(int nb) {
if (limit - position < nb)
throw new BufferOverflowException();
int p = position;
position += nb;
return p;
}
final int checkIndex(int i) {
if ((i < 0) || (i >= limit))
throw new IndexOutOfBoundsException();
return i;
}
final int checkIndex(int i, int nb) {
if ((i < 0) || (nb > limit - i))
throw new IndexOutOfBoundsException();
return i;
}
final int markValue() {
return mark;
}
final void truncate() {
mark = -1;
position = 0;
limit = 0;
capacity = 0;
}
final void discardMark() {
mark = -1;
}
static void checkBounds(int off, int len, int size) {
if ((off | len | (off + len) | (size - (off + len))) < 0)
throw new IndexOutOfBoundsException();
}
}
Buffer中有四個重要的值,分別是mark、limit、position、capacity:
1.capacity:表示一個Buffer所分配的最大容量,一般在初始化Buffer對象是指定,如:
ByteBuffer buffer = ByteBuffer.allocate(1024);
同ByteBuffer的靜態方法爲buffer對象指定的了1024個字節的空間。
2. position:指當前可寫入或讀取的位置,默認值爲0,當向buffer中寫入一定字節的數據時,positon的值會處於之前寫入值得後一個字節且position<=capacity。通常我們在向一個buffer中寫入數據之後,想要再讀取其中的數據,需要調用flip()方法,將position的值置爲0,以保證從第一個字節開始讀取。
3.limit:表示當前可以向buffer中寫入數據或者讀取數據的大小。通常在寫模式下,limit值與capacity相同,在讀模式下,limit值與寫模式下的position值相同(當寫完數據之後調用了flip()方法,position=0,limit的值與調用方法之前的position值相同)。
4.mark:用於標記當前讀取或寫入的位置,即記錄當前position的值,使得在調用reset() 方法時,可以從之前記錄的位置開始操作數據。
Buffer中的幾個重要方法:
1.flip():將limit值置爲position的值,並將position置爲1,通常用於寫操作轉換爲讀操作的過程。
2.clear():清空buffer的數據(實際並沒有清空數據),將mark、limit、position、capacity都還原爲初值。
3.mark()和reset():分別用於標記當前position位置,並與稍後還原。
三、Selector
Selector一般稱爲選擇器,可以用於監控控多個 SelectableChannel 的 IO ,即使用單線程來管理多個Channel,是非阻塞IO的核心。在傳統的Socketbian編程中,當客戶端向服務端發起多個請求時,一般情況下,服務端會開啓一個線程來處理對應的請求,但是如果客戶端想要與服務端保持長時間的連接,並且不是連續的向服務端發送請求,這時如果使用多線程會佔用太多無用的內存,因此需要使用Selector。
1.Selector的使用方式
(1)調用Selector的靜態方法open()創建一個Selector對象
Selector selector = Selector.open();
(2)將Channle設置爲非阻塞模式並註冊至監聽器
channel.configureBlocking(false);
SelectionKey key = channel.register(selector, Selectionkey.OP_READ);
這裏的channel必須是非阻塞的,一般繼承自SelectableChannel。
2.Selector監聽事件類型
channel的register方法的第二個參數表示selector的監聽事件類型,是一個interest集合,表示在selector監聽channel事件時,對什麼事件感興趣,包含以下四種監聽事件類型:
(1)SelectionKey.OP_READ:讀就緒
(2)SelectionKey.OP_WRITE:寫就緒
(3)SelectionKey.OP_CONNECT:連接就緒
(4)SelectionKey.OP_ACCEPT:接收就緒
當監聽器需要一次監聽多個事件,可以使用
int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;
3.SelectionKey
一個SelectionKey鍵值表示一個特定通道和一個選擇器之間的對應的註冊關係,包含以下幾種主要方法:
(1)key.interestOps():獲取選擇器監聽事件,可以通過以下方式來進行判斷:
int interestSet = key.interestOps();
System.out.println("isInterestedInAccept = " + ((interestSet & SelectionKey.OP_ACCEPT)
== SelectionKey.OP_ACCEPT));
System.out.println("isInterestedInConnect = " + ((interestSet & SelectionKey.OP_CONNECT)
== SelectionKey.OP_CONNECT));
System.out.println("isInterestedInRead = " + ((interestSet & SelectionKey.OP_READ)
== SelectionKey.OP_READ));
System.out.println("isInterestedInWrite = " + ((interestSet & SelectionKey.OP_WRITE)
== SelectionKey.OP_WRITE));
(2)key.selector():返回對應的selector
(3)key.channel():返回對應的channel
(4)key.readyOps():表示通道所處的就緒操作的集合
(5)key.isReadable():是否可讀,boolean類型
(6)key.isWritable():是否可寫,boolean類型
(7)key.isConnectable():是否可以連接,boolean類型
(8)key.isAcceptable():是否可以接收,boolean類型
四、一個簡單的客戶端與服務端交互的實例
1.服務端
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
public class NIOServer {
private static Map<SelectionKey, StringBuilder> selectionMap = new HashMap<SelectionKey, StringBuilder>();
public static void main(String[] args) {
ServerSocketChannel serverSocketChannel = null;
Selector selector = null;
try {
serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress(9999));
// 將channel設置爲非阻塞
serverSocketChannel.configureBlocking(false);
selector = Selector.open();
// 將channel註冊時選擇器,並指定“感興趣”的事件爲accept
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
int select = selector.select();
System.out.println("select = " + select);
Set<SelectionKey> keySet = selector.selectedKeys();
Iterator<SelectionKey> iterator = keySet.iterator();
while (iterator.hasNext()) {
SelectionKey selectionKey = iterator.next();
if (selectionKey.isAcceptable()) {
ServerSocketChannel serverChannel = (ServerSocketChannel) selectionKey.channel();
SocketChannel socketChannel = serverChannel.accept();
if (null == socketChannel) {
continue;
}
// 將獲取的連接註冊至選擇器,並監聽讀事件
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);
} else if (selectionKey.isReadable()) {
SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
StringBuilder sb = selectionMap.get(selectionKey);
if (null == sb) {
sb = new StringBuilder();
selectionMap.put(selectionKey, sb);
}
ByteBuffer buffer = ByteBuffer.allocate(10);
int n = -1;
while ((n = socketChannel.read(buffer)) > 0) {
System.out.println(n);
buffer.flip();
sb.append(new String(buffer.array(), 0, n));
buffer.clear();
}
System.out.println(n);
if (n == -1) {
selectionMap.remove(selectionKey);
System.out.println("receive message = " + sb.toString());
// 關閉輸入
socketChannel.shutdownInput();
// 將感興趣事件改爲寫
selectionKey.interestOps(SelectionKey.OP_WRITE);
}
} else if (selectionKey.isWritable()) {
SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.put("Hello Client".getBytes());
buffer.flip();
socketChannel.write(buffer);
// 關閉輸出
socketChannel.shutdownOutput();
socketChannel.close();
selectionKey.channel();
}
iterator.remove();
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (null != serverSocketChannel) {
serverSocketChannel.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
2.客戶端
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
public class NIOClient {
public static void main(String[] args) {
SocketChannel socketChannel = null;
try {
socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9999));
socketChannel.configureBlocking(false);
ByteBuffer buffer = ByteBuffer.allocate(10240);
buffer.put("Hello Server ".getBytes());
buffer.flip();
socketChannel.write(buffer);
// 關閉輸出
socketChannel.shutdownOutput();
buffer.clear();
StringBuilder sb = new StringBuilder();
int n = -1;
while ((n = socketChannel.read(buffer)) != -1) {
sb.append(new String(buffer.array(), 0, n));
buffer.clear();
}
// 關閉輸入
socketChannel.shutdownInput();
System.out.println("receive message = " + sb.toString());
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (null != socketChannel) {
socketChannel.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}