首先分享之前的所有文章 , 欢迎点赞收藏转发三连下次一定 >>>> 😜😜😜
文章合集 : 🎁 juejin.cn/post/694164…
Github : 👉 github.com/black-ant
CASE 备份 : 👉 gitee.com/antblack/ca…
一 . 前言
NIO 是老生常谈了 , 由于最近准备开 Netty 的新坑了 , 不再局限于使用 , 初期先把前置的知识点回顾一下
二 . NIO 的概念
2.1 NIO 是什么 ?
NIO 可以从2个维度说 ,它既可以是一种设计模型 ,也可以说是 Java 中的一个包 (NIO 包是在 java1.4中引入的).
IO 是计算机与其他部分之间的接口 , IO 可以分为多种 : BIO ,NIO , AIO
NIO 模型
2.2 NIO , AIO , BIO 的区别
BIO : 基于流( Stream ) 的阻塞 IO , 也是我们最常见的 IO
NIO : 通过 Selector 实现的基于缓冲区 (Buffer) 的非阻塞式 IO
AIO : 在 NIO 基础上引入了异步的概念
IO 流向和特点:
1 | java复制代码 |
PS : 这里的单线程处理是指只会在一个线程里面通过 selector 来处理逻辑 , 然后由 select 指定具体的 Handler
线程的模式
1 | java复制代码BIO :一个连接一个线程,客户端的连接请求时服务器端就需要启动一个线程进行处理 |
2.3 NIO 的组成部分
Channel
s and Buffer
s , 是 NIO 的中心对象,几乎用于每一个 I/O 操作 .
Channels : 频道
- Channel 是一个对象,可以从中读取数据并向其写入数据
- 类似于 Stream , 去任何地方(或来自任何地方)的数据都必须通过 Channel 对象
- 数据可以从 channel 写入buffers ,也可以从 buffers 读取到 channels , channels 的 读写均为单向
- 通道中的数据总是要先读到一个Buffer,或者总是要从一个Buffer中写入
- 通道可以异步地读写
Buffers : 缓冲区
- Buffer 是一个 Java 对象
- 缓冲区本质上是一个数组 , 通常是字节数组 , 也可以是其他数组
- 缓冲区提供对数据的结构化访问,并且跟踪系统的读/写进程。
- 发送到通道的所有数据必须首先放置在缓冲区中; 同样,从通道读取的任何数据都被读入缓冲区
Selectors :
- Selector允许单线程处理多个 Channel , 当打开了多个连接(通道),但每个连接的流量都很低,使用Selector就会很方便 .
- 要使用Selector,得向Selector注册Channel,然后调用它的select()方法。
- 这个方法会一直阻塞到某个注册的通道有事件就绪。
- 一旦这个方法返回,线程就可以处理这些事件,事件的例子有如新连接进来,数据接收
2.4 Channel 的主要实现
Channel的类型 :
- FileChannel — 从文件中读写数据
- DatagramChannel — 能通过UDP读写网络中的数据
- SocketChannel — 能通过TCP读写网络中的数据
- ServerSocketChannel — 可以监听新进来的TCP连接,像Web服务器那样
2.5 Buffer 的主要实现
Buffer 的主要实现 : ByteBuffer / CharBuffer / DoubleBuffer / FloatBuffer / IntBuffer / LongBuffer / ShortBuffer
每个 Buffer 类都是 Buffer 接口的一个实例。除了 ByteBuffer 之外(基础对象),每一个都具有完全相同的操作,只是所处理的数据类型不同。
Buffer 方法简述
1 | java复制代码- array() : 返回返回该缓冲区的数组 |
要点 :
当向buffer写入数据时,buffer会记录下写了多少数据。
一旦要读取数据,需要通过flip()方法将Buffer从写模式切换到读模式。
在读模式下,可以读取之前写入到buffer的所有数据
读取完成后 需要清空缓存区 , 任何未读的数据都被移到缓冲区的起始处,新写入的数据将放到缓冲区未读数据的后面 , 此时可以使用如下方法
clear() : 清空整个缓存区
compact() : 清除已经读过的数据
capacity,position 和 limit 三属性
- capacity (总容量)
作为一个内存块,Buffer有一个固定的大小值,也叫“capacity”.你只能往里写capacity个byte、long,char等类型
- position(指针当前位置)
当你写数据到Buffer中时,position表示当前的位置。初始的position值为0
当将Buffer从写模式切换到读模式,position会被重置为0
当从Buffer的position处读取数据时,position向前移动到下一个可读的位置
- limit (读/写边界位置)
在写模式下,Buffer的limit表示你最多能往Buffer里写多少数据。 写模式下,limit等于Buffer的capacity
当切换Buffer到读模式时, limit表示你最多能读到多少数据
- mark
用于记录当前 position 的前一个位置或者默认是 0在实际操作数据时它们有如下关系图
参考自 @ 一文让你彻底理解 Java NIO 核心组件 - SegmentFault 思否
在对Buffer进行读/写的过程中,position会往后移动,而 limit 就是 position 移动的边界。
- 在对Buffer进行写入操作时,limit应当设置为capacity的大小
- 对Buffer进行读取操作时,limit应当设置为数据的实际结束位置
2.6 Select 的主要实现
三 . Java NIO 使用
3.1 一个 NIO 的简单案例
File 读取
1 | java复制代码 |
File 写入
1 | java复制代码 public void templateWrite() throws Exception { |
3.2 scatter/gather 概念
scatter/gather用于描述从Channel 中读取或者写入到Channel的操作
- 分散(scatter)从Channel中读取是指在读操作时将读取的数据写入多个buffer中
- Channel将从Channel中读取的数据“分散(scatter)”到多个Buffer中
- 聚集(gather)写入Channel是指在写操作时将多个buffer的数据写入同一个Channelsc
- Channel 将多个Buffer中的数据“聚集(gather)”后发送到Channel
1 | java复制代码// ------------------- Scattering Reads |
3.2 数据传输
在Java NIO中,如果两个通道中有一个是FileChannel,那你可以直接将数据从一个channel传输到另外一个channel
3.2.1 transferFrom 将数据从源通道传输到FileChannel中
1 | java复制代码RandomAccessFile fromFile = new RandomAccessFile("fromFile.txt", "rw"); |
3.2.2 transferTo() 将数据从FileChannel传输到其他的channel中
1 | java复制代码RandomAccessFile toFile = new RandomAccessFile("toFile.txt", "rw"); |
3.3 Selector
Selector(选择器) 是Java NIO中能够检测一到多个NIO通道,并能够知晓通道是否为诸如读写事件做好准备的组件。
这样,一个单独的线程可以管理多个channel,从而管理多个网络连接
仅用单个线程来处理多个Channels的好处是,只需要更少的线程来处理通道
3.3.1 Selector 的 创建
1 | java复制代码// 创建 selector |
3.3.2 ServerSocketChannel
1 | java复制代码/** |
3.3.3 SocketChannel
Java NIO中的SocketChannel是一个连接到TCP网络套接字的通道
创建 SocketChannel 2 种方式 :
- 打开一个SocketChannel并连接到互联网上的某台服务器
- 一个新连接到达ServerSocketChannel时,会创建一个SocketChannel
1 | java复制代码// --> 打开 SocketChannel |
3.3.4 FileChannel
通过使用一个InputStream、OutputStream或RandomAccessFile来获取一个FileChannel实例
从FileChannel读取数据 :
- 首先,准备一个Buffer
- 然后,调用FileChannel.read()方法 ,该方法将数据从FileChannel读取到Buffer中
1 | java复制代码RandomAccessFile aFile = new RandomAccessFile("data/local-data.txt", "rw"); |
3.3.5 一个 Selector 的完整使用
该段代码参考至 Java NIO原理 图文分析及代码实现 , 写的很清楚 , 代码复制下来可以直接用 , 建议试试
1 | java复制代码public void startLogin() { |
附上客户端逻辑
1 | java复制代码public void startLogin() { |
3.3.6 AIO Demo
参考 JAVA中BIO、NIO、AIO的分析理解-阿里云开发者社区 (aliyun.com) , 这个文档里面把三种类型都通过 Demo 的形式展现 , 非常清晰
截取几段 AIO 核心代码 :
1 | java复制代码public void init() { |
五 . NIO 性能分析
TODO : 性能分析当然是要做的 , 后续补上 , 见谅
六 . NIO 的其他用法
6.1 Java NIO Pipe 用法
Pipe有一个source通道和一个sink通道。数据会被写到sink通道,从source通道读取
1 | java复制代码// > 开启管道 |
七 .深入学习
@ Getting started with new I/O (NIO) – IBM Developer
@ Java NIO - Quick Guide - Tutorialspoint
总结 :
之前一直对这些概念没有系统的了解 ,这里算是补上了
整个学习的过程中陆陆续续发现了很多优秀的博客 ,从中也学习到了很多东西 , 这里全部放在参考文档中了
另外 , 学习 Java NIO 只是为了了解概念 ,Netty 的 NIO 中大量代码是自己原生定制的 , 解决了很多原本Java NIO 的问题
参考文档 :
Getting started with new I/O (NIO) – IBM Developer
Java NIO 教程(七) FileChannel - 简书 (jianshu.com)
Java NIO原理 图文分析及代码实现 - 逸情公子 - ITeye博客
JAVA中BIO、NIO、AIO的分析理解-阿里云开发者社区 (aliyun.com)
本文转载自: 掘金