- NIO简介
- 缓冲区Buffer
- capacity属性
- position属性
- limit属性
- 通道Channel
- 常用通道
- 和Buffer的联系
- 常用方法
- 选择器Selector
- 实战
Java NIO属于IO多路复用模型,NIO是面向缓冲区。数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动,这就增加了处理过程中的灵活性,使用它可以提供非阻塞式的高伸缩性网络,Java NIO的非阻塞模式,使一个线程从某通道发送请求或者读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取,而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。非阻塞写也是如此,一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。NIO可以做到用一个线程来处理多个操作的。假设有10000个请求过来,根据实际情况,可以分配50或者100个线程来处理。不像之前的阻塞IO那样,非得分配10000个。它类库包含以下三个核心组件:
- Channel(通道)
- Buffer(缓冲区)
- Selector(选择器)
缓冲区Buffer
- 通道的读取,就是将数据从通道读取到缓冲区中
- 通道的写入,就是将数据从缓冲区写入通道中
capacity属性NIO的Buffer内部是一个内存块(数组),与普通的内存块(Java数组)不同的是:NIO Buffer对象提供了一组比较有效的方法,用来进行写入和读取的交替访问。
position属性Buffer类的capacity属性表示内部容量的大小。一旦写入的对象数量超过了capacity,缓冲区就满了,不能再写入了。capacity并不是指内部的内存块byte[]数组的字节数量,而是指能写入的数据对象的最大限制数量。
limit属性Buffer类的position属性表示当前的位置。在刚进入写模式时,position值为0,表示当前的写入位置为从头开始。初始的position值为0,最大可写值为limit-1。读模式时,position会被重置为0。
在写模式下,limit属性值的含义为可以写入的数据最大上限。在刚进入写模式时,limit的值会被设置成缓冲区的capacity值,表示可以一直将缓冲区的容量写满。
在读模式下,limit值的含义为最多能从缓冲区读取多少数据。
public class BasicBuffer {
public static void main(String[] args) {
// 创建缓冲区,可以存3个int类型的数据
IntBuffer intBuffer = IntBuffer.allocate(3);
for (int i = 0; i < intBuffer.capacity(); i++) {
intBuffer.put(i * 1); // 一共3个int类型数据
}
// 切换到读模式
intBuffer.flip();
while (intBuffer.hasRemaining()) {
System.out.println(intBuffer.get());
}
}
}
通道Channel
常用通道在NIO中,一个网络连接使用一个通道表示,所有NIO的IO操作都是通过连接通道完成的。一个通道类似于OIO中输入流和输出流的结合体,既可以从通道读取数据,也可以向通道写入数据。Java NIO中一个socket连接使用一个Channel来表示。从更广泛的层面来说,一个通道可以表示一个底层的文件描述符,例如硬件设备、文件、网络连接等。然而,远不止如此,Java NIO的通道可以更加细化。例如,不同的网络传输协议类型,在Java中都有不同的NIO Channel实现。
- FileChannel:文件通道,用于文件的数据读写。
- SocketChannel:套接字通道,用于套接字TCP连接的数据读写。
- ServerSocketChannel:服务器套接字通道(或服务器监听通道),允许我们监听TCP连接请求,为每个监听到的请求创建一个SocketChannel通道。
- DatagramChannel:数据报通道,用于UDP的数据读写。
- read() 从通道读取数据并写入缓冲区
- write() 把缓冲区的数据读出来并写入到通道中
read()读取通道的数据时,对于通道来说是读模式(方法的使用者为动作操作者),对于ByteBuffer缓冲区来说是写入数据,这时ByteBuffer缓冲区处于写模式
选择器Selector
从模型意义上讲,选择器就是避免了通道准备数据过程中,程序代码被阻塞,当通道注册到选择中后,数据准备好后可以通知做相关业务。所以单独线程可以管理多个输入和输出通道。
通道和选择器之间的关联通过register(注册)的方式完成,可供选择器监控的通道IO事件(这里的IO事件不是对通
道的IO操作,而是通道处于某个IO操作的就绪状态,表示通道具备执行某个IO操作的条件)类型包括以下四种:
- 可读:SelectionKey.OP_READ
- 可写:SelectionKey.OP_WRITE
- 连接:SelectionKey.OP_CONNECT
- 接收:SelectionKey.OP_ACCEPT
并不是所有的通道都是可以被选择器监控或选择的。例如,FileChannel就不能被选择器复用。注册到选择器的通道必须处于非阻塞模式下,否则将抛出IllegalBlockingModeException异常。
实战
package nio;
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.Iterator;
public class DisCardServer {
public static void main(String[] args) throws IOException {
// 1.创建选择器
Selector selector = Selector.open();
// 2.创建通道
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
// 3.将通道设置为非阻塞
serverSocketChannel.configureBlocking(false);
// 4.绑定链接
serverSocketChannel.bind(new InetSocketAddress(18899));
// 5.将通道注册的“接收新连接”IO事件注册到选择器上
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
// 6.轮询感兴趣的IO就绪事件(选择键集合)
while (selector.select() > 0) {
//7.获取选择键集合
Iterator selectionKeys = selector.selectedKeys().iterator();
while (selectionKeys.hasNext()) {
// 8.获取单个的选择键,并处理
SelectionKey selectionKey = selectionKeys.next();
if (selectionKey.isAcceptable()) {
// 10.若选择键的IO事件是“连接就绪”,就获取客户端连接
SocketChannel socketChannel = serverSocketChannel.accept();
// 11.将新连接切换为非阻塞模式
socketChannel.configureBlocking(false);
// 12.将新连接的通道的可读事件注册到选择器上
socketChannel.register(selector, SelectionKey.OP_READ);
} else if (selectionKey.isReadable()) {
// 13.若选择键的IO事件是“可读”,则读取数据
SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
// 14.读取数据,然后丢弃
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
int length = 0;
while ((length = socketChannel.read(byteBuffer)) > 0) { // channel.read(buf)读取通道的数据时,对于通道来说是读模式,对于ByteBuffer缓冲区来说是写入数据,这时ByteBuffer缓冲区处于写模式
byteBuffer.flip();
byteBuffer.clear();
}
socketChannel.close();
}
// 15.移除选择键
selectionKeys.remove();
}
// 16.关闭连接
serverSocketChannel.close();
}
}
}
package nio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
public class DisCardClient {
public static void main(String[] args) throws IOException {
InetSocketAddress address =new InetSocketAddress("127.0.0.1",18899);
// 1.获取通道
SocketChannel socketChannel = SocketChannel.open(address);
// 2.切换成非阻塞模式
socketChannel.configureBlocking(false);
// 不断地自旋、等待连接完成,或者做一些其他的事情
while (!socketChannel.finishConnect()) {
}
// 3.分配指定大小的缓冲区
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
byteBuffer.put("hello world".getBytes());
byteBuffer.flip();
// 发送到服务器
socketChannel.write(byteBuffer);
socketChannel.shutdownOutput();
socketChannel.close();
}
}



