## 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专题五:持久化