本次文章主要给大家带来NIO的相关知识点,一些基础的理论知识,巩固大家的对NIO的理解。好了废话不多说直接步入正题。
一:NIO基础
什么是NIO?
顾名思义NIO:
non-blocking io也可称作new io非阻塞 IO
NIO的三大组件
1. Channel&Buffer
Channel:
- 基本概念:
Channel有点类似有stream,它是读写数据的双向通道,可以将数据读入buffer,也可以将buffer的数据写入Channel,而之前的stream要么是输入要么就是输出,由此可见Channel比stream更为底层
创建的Channel:
- FileChannel(文件的数据传输通道)
- DataGramChannel(UDP网络编程时的数据传输通道)
- SockeChannel(TCP编程时数据传输通道 客户端服务器都可使用)
- ServerSocketChannel(TCP编程时数据传输通道 专用于服务器)
- ......
Buffer
- 基本概念:
Buffer则是用来缓冲读写数据的。
常见的Buffer
- ByteBuffer
- MappedByteBuffer
- DirectByteBuffer(直接内存)
- HeapByteBuffer(堆内存)
- ShortBuffer
- IntBuffer
- LongBuffer
- FloatBuffer
- DoubleBuffer
- CharBuffer
2. Selector
- 多线程版的设计:
⚠️ 多线程版缺点
内存占用较高 线程上下文切换的成本高 只适合连接数少的场景
- 线程池版的设计
⚠️ 线程池版缺点
阻塞模式下,线程仅能处理一个socket连接 仅适合短链接场景
selector版的设计
selector基本概念 selector的作用就是配合一个线程来管理多个channel,获取这些channel上发生的事件,这些channel工作在非阻塞模式下,不会让线程吊死在一个channel上。适合连接数特别多,但流量低的场景(low traffic)
大致如图所示:
调用selector的select()会阻塞直到channel发生了读写就绪事件,这些事件发生,select方法就会返回这些事件交给thread来处理。
Channel和Buffer的小demo
Channel获取方式(FileChannel的获取)
- 通过输入输出流,可以间接的获取,
- 通过randomAccessFile
ByteBuffer获取方式通过静态方法ByteBuffer.allocate(16)
- TestByteBuffer
@Slf4j
public class TestByteBuffer {
public static void main(String[] args) {
// FileChannel
// 1. 输入输出流,
// 2. RandomAccessFile
// RandomAccessFile file = new RandomAccessFile("helloword/data.txt", "rw")
try (FileChannel channel = new FileInputStream("data.txt").getChannel()) {
// 准备缓冲区
ByteBuffer buffer = ByteBuffer.allocate(10);
while(true) {
// 从 channel 读取数据,向 buffer 写入
int len = channel.read(buffer);
log.debug("读取到的字节数 {}", len);
if(len == -1) { // 没有内容了
break;
}
// 打印 buffer 的内容
buffer.flip(); // 切换至读模式
while(buffer.hasRemaining()) { // 是否还有剩余未读数据
byte b = buffer.get();
log.debug("实际字节 {}", (char) b);
}
buffer.clear(); // 切换为写模式
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
data.txt的内容是:
1234567890abcd
ByteBuffer 的使用
- 向Buffer写入数据,例如调用channel.read(buffer);
- 调用读模式
flip() - 从buffer读取数据,调用buffer.get();
- 调用
clear()或compact()切换至写模式,后面介绍两种方法的不同之处。
ByteBuffer的结构
ByteBuffer有以下的重要属性:
- capacity 容量
- position 位置(位移)
- limit 限制(等于容量)
ByteBuffer初始化的时候:
写模式下,position是写入位置,limit等于容量,下图是写入了4个字节后的状态。
当发生filp()动作后,position切换为读取位置,limit切换为读取限制。
读取了4个字节后
clear动作发生后,ByteBuffer进行了初始化:
compact()方法,是把未读完的部分向前压缩,然后切换到写模式下,如图所示: (这里将clear和compact进行了讲解,大致区别也是如此)
ByteBuffer常见的方法
- 分配空间
使用allocate方法为ByteBuffer分配空间,其他的Buffer也有该方法。
例如:
ByteBuffer buf = ByteBuffer.allocate(10);
- 向ByteBuffer写入数据
方法有两种:- 调用
channel的read方法; - 调用buffer自己的put方法;
- 调用
例如:
int readBytes = channel.read(buffer);
buf.put((byte)127);
- 从buffer读取数据
方法同样有两种:-
- 调用
channel的write方法;
- 调用
-
- 调用buffer的get方法;
-
例如:
int writeBytes = channel.write(buf);
byte b = buf.get()
💡 注意:
buffer的
get()方法会让position读指针向后移动,如果想要重复读取数据:
- 可以调用rewind()方法将position重新置为0;
- 调用get(int i)获取索引 i 的内容,它不回移动读指针
ByteBuffer的mark和reset
mark是在读取的时候,做一个
标记,即使position发生改变,只要调用reset方法就能回到mark的位置
💡 注意:
rewind和flip都会清楚掉mark位置,前者是将position重置为0,后者是切换读模式,切记切记
字符串和ByteBuffer的互转
/**
* 1.字符串转化为ByteBuffer
* 此方法要想将ByteBuffer转换为字符串则需要切换为读模式-使用flip()方法 该方法还是写模式
*/
ByteBuffer buffer1 = ByteBuffer.allocate(16);
buffer1.put("hello".getBytes());
debugAll(buffer1);
/**
* 2.字符串转化为ByteBuffer
*/
ByteBuffer buffer2 = StandardCharsets.UTF_8.encode("hello");
debugAll(buffer2);
/**
* 3.字符串转化为ByteBuffer
*/
ByteBuffer buffer3 = ByteBuffer.wrap("hello".getBytes());
debugAll(buffer3);
/**
* ByteBuffer 转换为字符串
*/
String str3 = StandardCharsets.UTF_8.decode(buffer3).toString();
System.out.println(str3);
关于Netty入门暂时结束。如果有帮助,欢迎点赞关注。
微信搜索【
码上遇见你】获取更多精彩内容