## Java專題十一(2):NIO
[TOC]
### 0. NIO是什么
Non-blocking IO(非阻塞IO)
> NIO可以讓你非阻塞的使用IO,當線程從通道讀取數據到緩沖區時,線程還是可以進行其他事情。當數據被寫入到緩沖區時,線程可以繼續處理它。從緩沖區寫入通道也類似。
### 1. 通道Channel
> 通道代表面向實體的開放式連接,執行讀寫等IO操作,其中實體包括硬件設備,文件,網絡套接字等
通道`Channel`與緩沖區`Buffer`密不可分,不同于IO從字符或字節流中讀寫操作,`Channel`既可以從`Buffer`中讀取數據,也可以寫入數據到`Buffer`中
**類圖關系如下**:
- Channel
- InterruptibleChannel
- FileChannel
- SelectableChannel:能與選擇器`Selector`結合實現多路IO復用
- SocketChannel
- ServerSocketChannel
- DatagramChannel
- ReadableByteChannel:讀取Channel數據到Buffer中, `int read(ByteBuffer dst)`
- WritableByteChannel:寫入Buffer數據到Channel中,` int write(ByteBuffer src)`
`Channel`中一些重要方法:
| 方法 | 說明 |
| --- | --- |
| `open()` | 用于創建Channel對象,如`ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();` |
| `SelectionKey register(Selector sel, int ops)` | 只適用于`SelectableChannel`類型的通道,用于向Selector中注冊操作,當通道中有注冊過的操作時,可以對通道進行操作。ops包括4種: `SelectionKey.OP_ACCEPT`/`SelectionKey.OP_CONNECT`/`SelectionKey.OP_READ`/`SelectionKey.OP_WRITE` |
| `SelectableChannel configureBlocking(boolean block)` | 只適用于`SelectableChannel`類型的通道,用于配置是否阻塞模式,如`serverSocketChannel.configureBlocking(false);` |
| `int read(ByteBuffer dst)` | 將Channel里的數據讀取到Buffer中 |
| `int write(ByteBuffer src)` | 將Buffer中的數據寫入到Channel中 |
### 2. 緩沖區Buffer
> 相當于內存塊,可讀可寫
**類圖關系如下**:
- Buffer
- ByteBuffer:
- HeapByteBuffer
- CharBuffer:
- ShortBuffer:
- IntBuffer:
- LongBuffer:
- FloatBuffer:
- DoubleBuffer:
- DirectBuffer
- DirectByteBuffer
`ByteBuffer`中一些重要方法:
| 方法 | 說明 |
| --- | --- |
| `allocate(int capacity)` | 用于創建容量為capacity的Buffer對象,如`ByteBuffer buffer = ByteBuffer.allocate(1024);` |
| `boolean hasRemaining()` | 是否有元素在`position`和`limit`之間|
| `ByteBuffer put(byte b)` | 將一個字節寫入Buffer中 |
| `ByteBuffer put(byte[] src)` | 將字節數組寫入Buffer中 |
| `byte get()` | 從Buffer中獲取一個字節 |
| `byte get(byte[] dst)` | 將Buffer中數據寫入字節數組中 |
| `ByteBuffer flip()` | 從寫模式切換為讀模式,`limit = positon; positon = 0; mark = -1` |
| `ByteBuffer rewind()` | `position = 0; mark = -1;` |
| `ByteBuffer clear()` | `position = 0; limit = capacity; mark = -1;` |
| `ByteBuffer reset()` | `position = mark;` |
`capacity`、`position`、`limit`說明:
- capacity:緩沖區的大小,最多可以存放capacity個數據
- position:寫模式下,代表寫入了多少個數據,使用`flip()`切換為讀模式時,position設為0
- limit:讀模式下,代表能讀取到的數據大小,使用`flip()`切換為讀模式時,limit設為position
### 3. 選擇器Selector
> 一個`Selector`可以處理多個`Channel`,通常與`SelectableChannel`類型的`Channel`結合使用
`Selector`中一些重要方法:
| 方法 | 說明 |
| --- | --- |
| `Selector open()` | 用于創建一個Selector對象,如`Selector selector = Selector.open();`|
| `int select()` | 選擇ready狀態的操作集(keys set)|
| `Set<SelectionKey> selectedKeys()` | 獲取選擇器已經選擇的操作集(keys set)|
### 4.一個完整示例
~~~
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.Iterator;
import java.util.Set;
public class Server {
public static void main(String[] args) throws Exception{
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress("127.0.0.1", 8017));
Selector selector = Selector.open();
serverSocketChannel.configureBlocking(false);
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
while(true){
selector.select();
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while(iterator.hasNext()){
SelectionKey key = iterator.next();
if(key.isAcceptable()){
System.out.println("key.isAcceptable()");
iterator.remove();
SocketChannel socketChannel = ((ServerSocketChannel)key.channel()).accept();
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);
}else if(key.isReadable()){
System.out.println("key.isReadable()");
iterator.remove();
SocketChannel socketChannel = (SocketChannel)key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
socketChannel.read(buffer);
System.out.println(new String(buffer.array()));
socketChannel.register(selector, SelectionKey.OP_WRITE);
}else if(key.isWritable()){
System.out.println("key.isWritable()");
iterator.remove();
SocketChannel socketChannel = (SocketChannel)key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.put(new String("i am server!!!").getBytes());
socketChannel.write(buffer);
System.out.println(new String(buffer.array()));
socketChannel.register(selector, SelectionKey.OP_READ);
}
}
}
}
}
~~~
1. 運行Server.java
2. 在CMD命令行輸入`telnet 127.0.0.1 8017 `,換行,輸入ab
3. 控制臺輸出如下:
~~~
key.isAcceptable()
key.isReadable()
a
key.isWritable()
i am server!!!
key.isReadable()
b
key.isWritable()
i am server!!!
~~~
- JavaCook
- Java專題零:類的繼承
- Java專題一:數據類型
- Java專題二:相等與比較
- Java專題三:集合
- Java專題四:異常
- Java專題五:遍歷與迭代
- Java專題六:運算符
- Java專題七:正則表達式
- Java專題八:泛型
- Java專題九:反射
- Java專題九(1):反射
- Java專題九(2):動態代理
- Java專題十:日期與時間
- Java專題十一:IO與NIO
- Java專題十一(1):IO
- Java專題十一(2):NIO
- Java專題十二:網絡
- Java專題十三:并發編程
- Java專題十三(1):線程與線程池
- Java專題十三(2):線程安全與同步
- Java專題十三(3):內存模型、volatile、ThreadLocal
- Java專題十四:JDBC
- Java專題十五:日志
- Java專題十六:定時任務
- Java專題十七:JavaMail
- Java專題十八:注解
- Java專題十九:淺拷貝與深拷貝
- Java專題二十:設計模式
- Java專題二十一:序列化與反序列化
- 附加專題一:MySQL
- MySQL專題零:簡介
- MySQL專題一:安裝與連接
- MySQL專題二:DDL與DML語法
- MySQL專題三:工作原理
- MySQL專題四:InnoDB存儲引擎
- MySQL專題五:sql優化
- MySQL專題六:數據類型
- 附加專題二:Mybatis
- Mybatis專題零:簡介
- Mybatis專題一:配置文件
- Mybatis專題二:映射文件
- Mybatis專題三:動態SQL
- Mybatis專題四:源碼解析
- 附加專題三:Web編程
- Web專題零:HTTP協議
- Web專題一:Servlet
- Web專題二:Cookie與Session
- 附加專題四:Redis
- Redis專題一:數據類型
- Redis專題二:事務
- Redis專題三:key的過期
- Redis專題四:消息隊列
- Redis專題五:持久化