文章目录
概述
NIO概述
- NIO支持面向缓冲区的、基于通道的IO操作。NIO将以更加高效的方式进行文件的读写操作
- NIO:new I/O或者非阻塞式IO
- 文件来自磁盘或者网络
字节流与NIO比较
-
字节流:理解为水管里字节流在流动,即直接面对字节数据流动(byte数组),且数据传输是单向的
-
NIO:如图,需要建立通道,通道用于连接源地点和目标地点,只是铁轨,没有火车(缓冲区),本身没法传输数据
-
NIO:通道+缓冲区(负责存储数据,火车 ),双向传输
- Buffer子类与通道交互(Buffer 主要用于与NIO 通道进行交互,数据是从通道读入缓冲区,从缓冲区写入通道中的)
-
boolean除外
-
关注缓冲区的核心属性
-
缓冲区底层是数组,所以大小不可改变
-
被遗忘状态指的是指针位置,容量,limit等未知
缓冲区编程
API
实例
- rewind指针重置
mark方法
put方法
直接缓冲区与非直接缓冲区
非直接缓冲区
- 应用程序与磁盘间无法直接传输文件,应用程序要先向操作系统发起读数据请求(底层调用read,面向OS的I/O接口)
- 用户地址空间(JVM内存)和内核地址空间(OS)
非直接缓冲区实例
- 参照缓冲区编程实例
直接缓冲区
- 复制多余,要省去copy过程
- JVM直接将缓冲区建立在OS的物理内存中
- 直接缓冲区:直接面向内存中的缓冲区
- 适用情况
- 大数据,不想在JVM中开辟空间
- 希望存放时间长的内容
- 缺点:
- 分配销毁资源较大,GC机制释放对物理内存映射文件的引用,才会释放物理内存
- 不易控制(交付之后管不了,由OS决定后续了)
直接缓冲区实例
- Path get(String first, String … more) : 用于将多个字符串串连成路径。
- 通过如上方式进行字符串连接
- 红圈处对应
- 内存映射文件(物理内存中文件,只有ByteBuffer支持)的另一种创建方式
- 此处缓冲区放,缓冲区取,不需要Channel(因为映射同一个文件)
- 若第一个红圈处只有write,代表通道只支持写,导致通过通道获取的内存映射文件也不支持读,所以要添加read权限
- 存在不稳定的问题:反馈不及时,因为GC机制没有及时释放内存(参照直接缓冲区的不安全性)——》程序不能及时退出(那边还没接收)
通道
-
Channel 本身不能直接访问数据,Channel 只能与Buffer 进行交互
-
通常情况:大量读写请求(CPU负责I/O控制)——》导致CPU占有率高——》不能处理其他事务,利用率低
-
改进:DMA(向CPU申请权限,DMA负责I/O),DMA全名为直接存储器存储——》与内存直接进行交互
-
问题:过多请求——》DMA总线过多,冲突
-
解决方法:
- 通道:相较于DMA,独立的处理器,专门负责I/O,不需要申请权限
API
通道间数据传输
- to和from对称关系,上述两种写法同样效果
- 遮挡处使用create_new:若2.mkv存在就报错,如果使用create,代表存在则覆盖
利用通道完成复制
异常处理
分散读取与聚集写入
- 将通道数据分散到多个缓冲区(依次填满)
- 多个缓冲区——》通道
- 和之前程序不同之处:操作缓冲区——》操作缓冲区数组
字符集
-
上述代码查看支持的字符集
- 首先获取编码器,解码器,然后创建缓冲区并放置数据,然后切换读模式,最后进行编码
- 编码:实质是字符buffer转字节buffer(转换为对应数值,存到底层) -
注意flip的使用及位置
-
编码与解码对应,不然乱码
-
以上为文件通道FileChannel
阻塞式网络通信
客户端
- 添加反馈如下,注意有要shutdown方法,表示接收完毕,图中没有显示
- 网络地址:套接字
- 获取通道
- 分配指定大小的缓冲区
- 从本地读取文件(FileChannel)——》数据读到缓冲区中(切换模式,读取到SocketChannel),边读边写
- 关闭通道
- 实际读取字节数(len)
服务端
- ServerSocketChannel
- 只需要指定端口号
- 获取通道
- 绑定连接
- 获取客户端连接的通道
- 接收客户端数据,并保存到本地
- 关闭通道
-
服务端:接收完毕,发送反馈给客户端
-
客户端:告诉服务端发完了
非阻塞式网络通信(❤)
- 利用Selector 可使一个单独的线程管理多个Channel
- Selector 是非阻塞IO 的核心
- 使用NIO完成网络通信(实质数据传输)的三个组件
- SelectableChannel抽象类
- 非阻塞式相较于网络通信,所以FileChannel不能切换为非阻塞式模式
图解
- 传统I/O:
- 类比本地系统——》服务端系统上同样有用户地址空间和内核地址空间,同样copy
- 服务器不能确认客户端读写请求的真实有效(有无数据)——》服务端线程一直处于阻塞,要等待有数据,才开始工作
- 客户端发送数据,先到内核地址空间(读取内核地址空间判断有无数据)——》有数据,copy到用户地址空间
- 大量请求——》排队,先来后到
- 为每一个请求分配一个线程,多线程解决IO阻塞问题——》但线程数量有限
- 非阻塞式
- 选择器:每个通道注册到选择器,监控通道I/O状态(读写,连接,接收数据状态)
- 某通道上某请求事件准备就绪时(数据准备就绪),选择器才会将任务分配到服务端的一个或多个线程上,再去运行
客户端
- 获取通道
- 切换为非阻塞模式
- 分配缓冲区
- 发送数据给服务端
- 关闭通道
服务端
- 获取通道
- 切换为非阻塞模式
- 绑定连接
- 获取选择器,注册,选择键(多个状态监听,使用位或)
-
获取所有监听的事件(iterator遍历)
-
接收就绪——》再接收(accept)客户端连接的套接字
-
客户端通道也需要切换为非阻塞式模式
-
监听两种状态(接收就绪与读就绪)
-
用完SelectionKey要取消掉(remove),不然一直有效,理由如下
-
某个通道已经处于连接完成状态,不取消——》下一次依然显示连接完成状态,继续执行对应逻辑
-
先启动服务端,然后客户端
-
while轮询状态,谁就绪,处理谁
-
聊天室的雏形
-
改进:对于不同的就绪状态,分配到多个线程执行后续任务
UDP
- 把发送内容都打成包发送过去,目的地相同
- 阻塞式轮询地获取就绪状态
管道
- 单向数据传输
- 可以两个线程共用一个pipe
- 一个发送,一个接收