Java NIO 基础
- 1. NIO 介绍
- 2. NIO 三大组件
- 2.1 Channel
- 2.1.1 常见的 Channel
- 2.1.2 常用方法
- 2.2 Buffer
- 2.2.1 常见的 Buffer
- 2.2.2 重要属性
- 2.2.3 常用方法
- 2.3 Selector
- 2.3.1 四种事件类型
1. NIO 介绍
NIO(non-blocking io)
:非阻塞IO,JDK1.4 引入。
2. NIO 三大组件
2.1 Channel
Channer
是读写数据的双向通道,类似于传统IO中的Stream
,但是Stream
只能单向操作。如:InputStream
只能读操作,OutputStream
只能写操作。
2.1.1 常见的 Channel
- FileChannel:文件IO通道,用于文件的读写
- DatagramChannel:UDP协议数据报通信
- SocketChannel:网络套接字IO通道,TCP协议客户端
- ServerSocketChannel:网络套接字IO通道,TCP协议服务端
2.1.2 常用方法
- read(ByteBuffer buffer):从
Channel
中读取到ByteBuffer
中,如果Channel
中没有数据会一直堵塞到可读 - read(ByteBuffer buffer,Long timeout):从
Channel
中读取到ByteBuffer
中,超过时间会报错 - write(ByteBuffer buffer):将数据写到
Channel
中,如果Channel
中没有可写空间会一直堵塞到可写 - write(ByteBuffer buffer, Long timeout):将数据写到
Channel
中,超过时间会报错 - flush():将Channel中缓冲区数据刷到底层设备
- register(Selector selector, SelectionKey key):将
Channel
注册到Selector
- configureBlocking(boolean b):设置
Channel
是否为阻塞模式 - socket():获取底层的
Socket
对象 - isConnected():
Channel
是否已经连接上 - isReadable():
Channel
是否可读 - isWriteable():
Channel
是否可写 - getRemoteAddress():
Channel
对应的远程地址 - getLocalAddress():
Channel
对应的本地地址 - open():
Channel
是否打开
2.2 Buffer
Buffer:缓冲读写数据,每一个Buffer
对象关联一个字节数组。
2.2.1 常见的 Buffer
- ByteBuffer
- CharBuffer
- ShortBuffer
- IntBuffer
- LongBuffer
- FloatBuffer
- DoubleBuffer
2.2.2 重要属性
- capacity:
Buffer
所占的内存大小,设置后不能修改 - limit:
Buffer
中可以操作的数据大小 - position:下一个要读/写的数据索引
- mark:标记当前
position
的位置,可以通过reset()
把position
恢复到mark
位置
2.2.3 常用方法
- capacity():返回
capacity
的值 - limit():返回
limit
的值 - limit(int n):设置
limit
的值 - position():返回
position
的值 - position(int n): 设置
position
的值 - mark():对Buffer做标记
- reset():把
position
恢复到mark
位置 - rewind():设置
position
为0,取消mark
标记 - hasRemaining():判断
Buffer
中是否有元素 - get():从
Buffer
中读取一个字节 - get(byte[] b):从
Buffer
中读取多个字节 - get(int index):从
Buffer
中读取指定索引位的字节 - put(byte b):往
Buffer
中存一个字节 - put(byte[] b):往
Buffer
中存多个字节 - put(int index, byte b):往
Buffer
指定索引位存字节 - clear():清空
Buffer
- compact():清空
position
之前的字节 - flip():将
limit
设置为position
的值,position
设置为0
2.3 Selector
Selector
配合一个线程管理多个Channel
,获取Channel
上发生的事件
2.3.1 四种事件类型
- OP_CONNECT:连接成功后(只客户端使用)
- OP_ACCEPT:客户端请求连接时(只服务端使用)
- OP_READ:读缓冲区有可读数据时
- OP_WRITE:写缓冲区有可写空间时
package com.learn.wesay;
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.nio.charset.StandardCharsets;
import java.util.Iterator;
public class Server {
public static void main(String[] args) throws IOException {
// 创建Selector管理多个Channel
Selector selector = Selector.open();
// 创建服务端套接字通道
ServerSocketChannel server = ServerSocketChannel.open();
// 设置为非阻塞
server.configureBlocking(false);
// 绑定端口
server.bind(new InetSocketAddress(8888));
// Channel注册到Selector,只处理accept事件
server.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
// 没有事件发生线程阻塞,有事件线程才会恢复运行
selector.select();
// 所有发生的事件
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey selectionKey = iterator.next();
if (selectionKey.isAcceptable()) {
handlerAccept(selectionKey, selector);
}
if (selectionKey.isReadable()) {
handlerRead(selectionKey, selector);
}
iterator.remove();
}
}
}
private static void handlerAccept(SelectionKey selectionKey, Selector selector) {
ServerSocketChannel server = (ServerSocketChannel) selectionKey.channel();
try {
SocketChannel channel = server.accept();
channel.configureBlocking(false);
channel.register(selector, SelectionKey.OP_READ);
System.out.println(channel);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private static void handlerRead(SelectionKey selectionKey, Selector selector) {
SocketChannel channel = (SocketChannel) selectionKey.channel();
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
try {
int read = channel.read(byteBuffer);
if (read == -1) {
// 客户端socket正常断开
selectionKey.cancel();
} else {
byteBuffer.flip();
System.out.println(StandardCharsets.UTF_8.decode(byteBuffer));
}
} catch (IOException e) {
// 客户端socket异常断开
selectionKey.cancel();
}
}
}
public class Client {
public static void main(String[] args) throws IOException {
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("localhost", 8888));
socketChannel.write(Charset.defaultCharset().encode("你好"));
}
}