Netty
NIO
non-blocking io —— 非阻塞IO
NIO 三大组件
- channel
1 | // FileChannel |
buffer
selector
ByteBuffer
ByteBuffer 使用
- 向buffer写入数据,例如调用 channel.read(buffer)
- 调用flip()切换至读模式
- 从buffer读取数据,例如调用buffer.get()
- 调用clear()或compact()切换至写模式
- 重复1~4步骤
ByteBuffer 结构
- capacity 容量
- position 起始位置
- limit 限制大小
ByteBuffer 方法
分配空间
allocate() -java 堆内存,读写效率较低,受GC影响
allocateDirect() -直接内存,读写效率高(少一次拷贝),不受GC影响,分配效率低
向buffer写入数据
调用channel.read()
调用buffer.put()
从buffer读取数据
调用channel.write()
调用buffer.get()
- get()会让position指针后移,重复读取:调用rewind()重置position 为0或者get(i)获取索引i的内容
- get(i)不会改变position位置
- mark & reset:mark记录position位置,reset回到mark记录的位置
字符串转ByteBuffer
1
2
3
4
5
6
7// 1.字符串str转为 ByteBuffer
ByteBuffer buffer1 = ByteBuffer.allocate(16);
buffer1.put(str.getBytes());
// 2.Charset
ByteBuffer buffer2 = StandardCharsets.UTF_8.encode(str);
// 3.wrap 字节数组跟ByteBuffer转换
ByteBuffer buffer3 = ByteBuffer.wrap(str.getBytes());- ByteBuffer转字符串
1
2
3
4
5// ByteBuffer转为字符串
buffer1.flip();
str = StandardCharsets.UTF_8.decode(buffer1).toString();
// Charset类型:会自动重置position,不用切换读模式
str = StandardCharsets.UTF_8.decode(buffer2).toString();
文件编程
FileChannel
FileChannel只能工作在阻塞模式下
- 获取
不能直接打开FileChannel,必须通过FilelnputStream、FileOutputStream或者RandomAccessFile来获取FileChannel,它们都有getChannel方法
- 通过FilelnputStream获取的channel只能读
- 通过FileOutputStream获取的channel只能写
- 通过RandomAccessFile是否能读写根据构造RandomAccessFile时的读写模式**(r / w)**决定
- 读取
会从channel读取数据填充ByteBuffer,返回值表示读到了多少字节,-1表示到达了文件的末尾
1 | int len = channel.read(buffer); |
- 写入
写入的正确姿势如下,SocketChannel
1 | ByteBuffer buffer =... |
在while中调用channel.write是因为write方法并不能保证一次将buffer中的内容全部写入channel
- 关闭
channel必须关闭,不过调用了FilelnputStream、FileOutputStream或者RandomAccessFile的close方法会间接地调用channel的close方法
- 位置
获取当前位置
1 | long pos = channel.position(); |
设置当前位置
1 | long newPos = ...; |
设置当前位置时,如果设置为文件的末尾
- 这时读取会返回-1
- 这时写入,会追加内容,但要注意如果p0sit0超过了文件末尾,再写入时在新内容和原末尾之间会有空洞(00)
- 大小
使用size方法获取文件的大小
- 强制写入
操作系统出于性能的考虑,会将数据缓存,不是立刻写入磁盘。可以调用force(true)方法将文件内容和元数据(文件的权限等信息)立刻写入磁盘
两个Channel传输数据
transferTo()
Path
Files
- 拷贝文件 (跟transferTo一样效率较高)
1 | Files.copy(source,target); |
walkFileTree();
网络编程
阻塞模式 vs 非阻塞模式
阻塞方法,线程停止运行
- 阻塞
- 非阻塞
- 多路复用
单线程可以配合Selector完成对多个Channel可读写事件的监控,这称之为多路复用
多路复用仅针对网络IO、普通文件IO没法利用多路复用
如果不用Selector的非阻塞模式,线程大部分时间都在做无用功,而Selector能够保证:
有可连接事件时才去连接
有可读事件才去读取
有可写事件才去写入
限于网络传输能力,Channel未必时时可写,一旦Channel可写,会触发Selector的可写事件
Selector
accept -会在有连接请求时触发
connect -是客户端,连接建立后触发
read -可读事件
write -可写事件
ServerSocketChannel是用来连接的,SocketChannel是用来处理客户端发的消息的
select():没有事件发生,阻塞,有事件,线程才会恢复运行。在事件未处理时,它不会阻塞。事件发生后要么处理要么取消。selectionKey .cancel()取消
Selector 最大的好处是可以通过阻塞方法select()拿到SelectionKey 判断时间类型从而做出不同的逻辑处理