java提供了IO、NIO、AIO包,用於處理輸入輸出。
BIO:Block IO 同步阻塞式 IO,就是我們平常使用的傳統 IO,它的特點是模式簡單使用方便,併發處理能力低。
NIO:Non IO 同步非阻塞 IO,是傳統 IO 的升級,客戶端和服務器端通過 Channel(通道)通訊,實現了多路複用。
AIO:Asynchronous IO 是 NIO 的升級,也叫 NIO2,實現了異步非堵塞 IO ,異步 IO 的操作基於事件和回調機制
IO
IO就是我們常用的 java.io包,它基於流模型,提供了File 抽象、輸入輸出流等。因爲是同步阻塞的,也有人稱爲BIO。好處就是可靠,返回後可以立刻知曉結果。
按數據分:字符流(InputStream、OutputStream)、字節流。
按操作分:輸入流、輸出流。
注:IO流的緩衝區是用來提高效率的。
NIO
NIO的三個主要組成部分:Channel(通道)、Buffer(緩衝區)、Selector(選擇器)。它們關係可以簡單下圖說明:
Channel
可以通過它讀取和寫入數據,類似IO中的流,不過它可以用於讀、寫或者同時用於讀寫。一個Channel綁定一個Buffer。
FileChannel
DatagramChannel
SocketChannel
ServerSocketChannel
Buffer
NIO有如下 buffer:
ByteBuffer, CharBuffer, DoubleBuffer, FloatBuffer, IntBuffer, LongBuffer, ShortBuffer
這些Buffer類型代表了不同的數據類型。換句話說,就是可以通過char,short,int,long,float 或 double類型來操作緩衝區中的字節。
Selector
Selector 對象上可以註冊很多個Channel,監聽各個Channel上發生的事件,當事件發生時決定Channel讀寫。這樣使用Selector就可以做到用一個線程來處理所有的channels,畢竟線程之間的切換的代價很高。
創建selector
Selector selector = Selector.open();
註冊channel的事件
channel.configureBlocking(false);// 注意這裏channel一定要設置成異步模式,不然報錯
SelectionKey key = channel.register(selector,SelectionKey.OP_READ);
對應事件說明:
事件 |
功能 |
對應判斷方法 |
SelectionKey.OP_ACCEP |
服務器監聽到了客戶連接,服務器可以接收這個連接 |
selectionKey.isAcceptable(); |
SelectionKey.OP_CONNECT |
連接就緒事件,表示客戶端與服務器的連接已經建立成功 |
selectionKey.isConnectable(); |
SelectionKey.OP_READ |
讀就緒事件,表示通道中已經有了可讀的數據,可以執行讀操作 |
selectionKey.isReadable(); |
SelectionKey.OP_WRITE |
寫就緒事件,表示已經可以向通道寫數據 |
selectionKey.isWritable(); |
如果要處理比較大的數據,從性能考慮建議用MappedByteBuffer。
看下示例:
public class MyMain {
public static void main(String[] args) throws IOException {
ByteBuffer sendbuffer = ByteBuffer.allocate(2048);
ByteBuffer receivebuffer = ByteBuffer.allocate(2048);
// 1 創建一個 Selector用於註冊。當事件發生時,seletor 告訴你發生了什麼
Selector selector = Selector.open();
ServerSocketChannel socketChannel = ServerSocketChannel.open();
socketChannel.configureBlocking(false);// 注意:一定要設置爲 非阻塞的
ServerSocket socket = socketChannel.socket();
socket.bind(new InetSocketAddress("127.0.0.1", 2181));// 綁定到給定的端口
// 註冊 accept 事件。值得一說的是因爲Channel一定要是異步的,因此這裏不能用 FileChannel
socketChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
// 這個方法會阻塞,直到至少有一個已註冊的事件發生
selector.select();
// 返回此選擇器的已選擇鍵集。
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
SelectionKey selectionKey = iterator.next();
iterator.remove();
handleKey(selector, selectionKey, sendbuffer, receivebuffer);
}
}
}
// 處理請求
private static void handleKey(Selector selector, SelectionKey selectionKey, ByteBuffer sendBuffer, ByteBuffer receivebuffer) throws IOException {
// 接受請求
SocketChannel client = null;
int count = 0;
// 測試此鍵的通道是否已準備好接受新的套接字連接。
if (selectionKey.isAcceptable()) {
System.out.println("通道連接已可用");
// 返回爲之創建此鍵的通道。
ServerSocketChannel server = (ServerSocketChannel) selectionKey.channel();
// 接受到此通道套接字的連接。
// 此方法返回的套接字通道(如果有)將處於阻塞模式。
client = server.accept();
// 配置爲非阻塞
client.configureBlocking(false);
// 註冊到selector,等待連接
client.register(selector, SelectionKey.OP_READ);
} else if (selectionKey.isReadable()) {
// 返回爲之創建此鍵的通道。
client = (SocketChannel) selectionKey.channel();
//將緩衝區清空以備下次讀取
receivebuffer.clear();
//讀取服務器發送來的數據到緩衝區中
count = client.read(receivebuffer);
if (count > 0) {
System.out.println("從客戶端獲取的數據:" + new String(receivebuffer.array(), 0, count));
client.register(selector, SelectionKey.OP_WRITE);
}
} else if (selectionKey.isWritable()) {
System.out.println("可向客戶端寫數據");
//將緩衝區清空以備下次寫入
sendBuffer.clear();
// 返回爲之創建此鍵的通道。
client = (SocketChannel) selectionKey.channel();
sendBuffer.put("Hellow world".getBytes());
sendBuffer.flip();
//輸出到通道
client.write(sendBuffer);
System.out.println("服務器端向客戶端發送數據");
client.register(selector, SelectionKey.OP_READ);
}
}
}
啓動後,輸入 http://localhost:2181/ 後,就會在控制檯發現有請求和可讀時間發現
AIO
即java NIO2。NIO是同步非阻塞的,提升了性能。但還是異步的。NIO2是異步的,因此也稱即AIO,即Asynchronous IO。異步 IO 操作是基於事件和回調機制的。
看下例子: