NIO
NIO(non-blocking IO)是jdk1.4之后提供的一套API,
可以替代原来的标准IO,
支持面向缓冲区的、基于通道的IO操作,
可以更高效地进行IO操作。
NIO有3个核心组件:
Channel,
Buffer,
Selector。
Channel
Channel是连接数据源头与目的地的通道,
CHannel不直接操作数据,Channel通过Buffer来进行交互,
Channel是Buffer的载体。
获取Channel
SocketChannel是Channel的实现类。
// 通过open()获取Channel
SocketChannel channel = SocketChannel.open();
Buffer
Buffer是一个数据对象,可以理解为固定大小的容器。
NIO中的数据访问,
都是通过缓冲区Buffer来进行操作的,
读取数据时从缓冲区读取,写入数据时写入到缓冲区。
缓冲区两个核心方法
put() 写入到缓冲区;
get() 从缓冲区读取。
缓冲区四个核心属性
capacity:
容量,缓冲区最大可以存储的数据量,声明后不能更改;
limit:
界限,缓冲区中可以操作数据的范围;
position:
位置,缓冲区中正在操作数据的位置;
mark:
标记,记录当前position,可以通过reset()恢复到mark的位置。
注意:
0 <= mark <= position <= limit <= capacity
获取Buffer
ByteBuffer是Buffer的实现类。
// 通过allocate()获取Buffer,需要声明大小。
ByteBuffer buffer = ByteBuffer.allocate(1024);
// 通过flip()切换读写模式
buffer.flip();
非直接缓冲区
通过allocate()分配的缓冲区,建立在jvm内存之中。
操作系统出于安全方面的考虑,一般不会允许应用程序和磁盘之间直接进行传输,
如果我们的应用程序想从磁盘读取数据,要分以下几步走:
应用程序向操作系统发出读请求;
操作系统将磁盘数据加载到内核地址空间;
再把内核地址空间中的数据copy到用户地址空间,在这儿就是jvm;
然后在应用程序读取。
同样,如果我们的应用程序想往磁盘写数据,
也要先写到用户地址空间,然后copy到内核地址空间,再写入到磁盘中。
直接缓冲区
通过allocateDirect()分配的缓冲区,建立在物理内存之中。
数据的读写直接在物理内存中进行。
Selector
Selector允许一个线程处理多个Channel,
Selector会轮训注册在它上面的所有Channel,如果某个Channel为读写做好准备,处于就绪状态,
Selector就会对其进行后续的IO操作,
不会同步阻塞等待,这就是non-blocking。
因为线程不会一直等待IO条件准备就绪,如果某个Channel没有就绪,会转而去处理其它Channel。
就绪状态:
SelectionKey.OP_READ:可读;
SelectionKey.OP_WRITE:可写;
SelectionKey.OP_CONNECT:连接;
SelectionKey.OP_ACCEPT:接收。
当Channel处于某个就绪状态,就会被Selector查询到,然后执行相应的操作。
传统的IO,面向IO流,每一个线程对应一个连接,如果IO尚未处于就绪状态,线程就会阻塞等待。
获取Selector
// 通过open()获取Selector
Selector selector = Selector.open();
完整实例
Server
package com.example.duohoob.nio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
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 NioServer {
public static void main(String[] args) throws IOException {
// 创建ServerSocketChannel
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
// 绑定到本地8088端口
SocketAddress endpoint = new InetSocketAddress(8088);
serverSocketChannel.socket().bind(endpoint);
// 设置非阻塞
serverSocketChannel.configureBlocking(false);
// 通过open()获取Selector
Selector selector = Selector.open();
// 将Channel注册到Selector,需要指定关注的就绪状态,多个用'|'分割。
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); // 接收就绪状态
while (true) {
// 轮询已就绪Channel
selector.select();
// 遍历已就绪事件selectionKeys
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
SelectionKey selectionKey = (SelectionKey) iterator.next();
// 处理连接事件
if (selectionKey.isAcceptable()) {
// 接收SocketChannel
SocketChannel socketChannel = serverSocketChannel.accept();
// 设置非阻塞
socketChannel.configureBlocking(false);
// 将SocketChannel注册到Selector,指定关注可读就绪状态。
socketChannel.register(selector, SelectionKey.OP_READ);
}
// 处理可读事件
if (selectionKey.isReadable()) {
// 通过allocate()获取Buffer,需要指定大小。
ByteBuffer buffer = ByteBuffer.allocate(1024);
// 读取
SocketChannel channel = (SocketChannel) selectionKey.channel();
channel.read(buffer);
System.out.println(new String(buffer.array(), "utf-8"));
}
// 处理完毕,移除。
iterator.remove();
}
}
}
}
Client
package com.example.duohoob.nio;
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) throws IOException {
// 创建SocketChannel
SocketChannel socketChannel = SocketChannel.open();
// 设置非阻塞
socketChannel.configureBlocking(false);
// 服务端
InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 8088);
if (!socketChannel.connect(inetSocketAddress)) {
while (!socketChannel.finishConnect()) {
// 连接服务端
}
}
// 获取Buffer
String message = "message from client.";
ByteBuffer buffer = ByteBuffer.wrap(message.getBytes("utf-8"));
// 写入
socketChannel.write(buffer);
// 关闭SocketChannel
socketChannel.close();
}
}