NIO学习笔记

NIO比IO强在哪

NIO,即非阻塞的IO,传统IO指的是BIO,使用流来进行文件读写,而NIO使用Channel(通道)和Buffer(缓冲)来进行文件读写。NIO是双全工,非阻塞的。

NIO的"强"主要体现在网络中

1.NIO支持非阻塞IO(ServerSocketChannel 和 SocketChannel),NIO在执行IO操作时不会阻塞,可以继续执行其他的代码;

2.NIO支持多路IO复用(Selector),一个线程可以监听多个通道的请求(接受连接,完成连接,可读,可写)

3.NIO提供了ByteBuffer类,可以高效地管理缓冲区。

ServerSocketChannel和SocketChannel

用于实现非阻塞IO,ServerSocketChannel负责监听连接请求,当设置非阻塞模式后,在等待连接请求时不会阻塞线程,SocketChannel负责发送连接请求和进行数据的读写,当设置成非阻塞模式时,在读写数据时不会阻塞线程。

Selector

用于实现IO多路复用,channel能通过register注册到一个Selector选择器上,这个选择器会不断轮询注册在其上面的所有通道,当通道发生事件时(连接请求,可读,可写),Selector就把通道放到一个就绪集合中,通过get方法可以获取到就绪的Channel并对其进行相应的操作。

注意: 由于 JDK 使用了 epoll() 代替传统的 select 实现,所以它并没有最大连接句柄 1024/2048 的限制。这也就意味着只需要一个线程负责 Selector 的轮询,就可以接入成千上万的客户端。

Selector可以监听的四种类型

1.SelectionKey.OP_ACCEPT:表示接收连接的事件,当ServerSocketChannel收到来自SocketChannel的连接请求时。这个ServerSocketChannel会有OP_ACCEPT事件

2.SelectionKey.OP_CONNECT:表示连接成功事件,当ServerSocketChannel同意来自SocketChannel的连接请求时。这个SocketChannel会有OP_CONNECT事件

3.SelectionKey.OP_READ:表示有数据可读

4.SelectionKey.OP_WRITE:表示可以写入数据

一个Selector实例控制的3个SelectionKey集合

1.所有注册在该Selector实例上的Channel的集合,通过keys()方法获取

2.所有需要进行io操作的Channel的集合,通过selectedKeys()方法获取

3.所有已经删除的Channel的集合

Selector核心方法

select():监控所有注册的Channel,当有需要io操作的Channel出现时,select方法会返回,并且将对应的SelectorKey加入到需要进行io操作的Channel集合中。

Buffer

缓冲区,NIO对数据的操作是利用Channel和基于Buffer进行的,Buffer是一个抽象类,用得比较多的实现类是ByteBuffer,缓冲区的2个重要操作是put和get。因为我们拿到一个缓冲区时,要么是往缓冲区里传输数据,要么是从缓冲区里拿数据。

Buffer的4个变量(limit,position,Capacity,Mark)

然后要说的就是Buffer里面的4个核心变量了,与存取数据操作密切相关。

limit 缓冲区里数据的总数

position 下一个要被读写的位置,调用get和put都会导致position的改变

Capacity容量,是创建缓冲区时的容量,不能被改变,因为缓冲区底层是数组。

Mark 记录上一次读写的位置

Buffer的基础操作

创建缓冲区

ByteBuffer b =ByteBuffer.allocate(1024)

此时四个变量的变化:

limit 1024

position 0

Capacity 1024

Mark java.nio.HeapByteBuffer[pos=0 lim=1024 cap=1024]

往缓冲区添加数据

b.put("我是高手".getBytes);

此时四个变量的变化:

limit 1024

position 12(这里UTF-8一个中文是3个字节,一共12个字节)

Capacity 1024

Mark java.nio.HeapByteBuffer[pos=12 lim=1024 cap=1024]

如果要从缓冲区取出数据,必须先调用flip方法(翻转,为什么叫翻转呢?先往下看)

b.flip();

此时四个变量的变化:

limit 12

position 0

Capacity 1024

Mark java.nio.HeapByteBuffer[pos=0 lim=12 cap=1024]

方法命名为翻转是针对position和limit这2个位置之间的区域来说的,调用flip方法之前是12~1024,调用之后变成了0~12。就像翻转过来了一样。

翻转后就可以取数据了,首先定义一个大小为limit的字节数组(因为只有这么多字节的数据),然后调用get方法将数据存到这个字节数组中。

byte [] bytes =new byte[b.limit()]

b.get(bytes);

此时四个变量又有了变化

limit 12

position 12 这里变成了12

Capacity 1024

Mark java.nio.HeapByteBuffer[pos=12 lim=12 cap=1024]

Channel 通道

buffer负责读写数据,channel负责传输数据?

通道可以分为文件通道和套接字通道

FileChannel,SocketChannel,ServerSocketChannel,DatagramChannel 都属于通道

FileChannel

建立通道:调用FileChannel的open静态方法建立通道,第一个参数是文件路径,第二个参数是访问模式(只读,读写)

FileChannel sourceChannel = FileChannel.open(
    Paths.get("logs/javabetter/itwanger.txt"), StandardOpenOption.READ);

使用FileChannel和ByteBuffer将source文件复制到destination文件中思路:

1.首先分别调用open方法创建内存与source文件之间的通道和内存与destination文件之间的通道。

2.调用ByteBuffer的allocate方法创建一个ByteBuffer缓冲区,大小随意,反正复制的时候是一个字节一个字节复制的。

3.开启while循环,调用source文件的通道的read方法,将source文件读取到缓冲区,缓冲区调用flip方法翻转,然后调用destination文件的通道的write方法将缓冲区写入destination文件中去,再调用一次缓冲区的clear方法重置缓冲区的四个变量以及内容。

4.当read方法返回-1时结束

使用MapperByteChannel(内存映射文件)和FileChannel将source文件复制到destination文件中思路:

1.同样的先分别创建2个文件的通道

2.一个MapperByteBuffer对象需要用FileChannel对象的map()方法创建,表示一个文件的映射,创建source和destination的内存映射(即2个MapperByteBuffer对象)。

3.循环读取source的MapperByteBuffer,并写入destination的MapperByteBuffer。

4.调用destination的force方法刷盘,如果不调用,也会刷盘,只不过可能不会立即刷盘。

使用transfer()和FileChannel将source文件复制到destination文件中思路:

1.同样的先分别创建2个文件的通道

2.sourceChannel.transferTo(0, sourceChannel.size(), destinationChannel);调用sourceChannel的transferTo方法。transferTo方法会返回该次操作写的字节数。

底层使用了零拷贝,最大限度的减少了数据传输过程中的CPU和内存开销

直接缓冲区和非直接缓冲区

1.非直接缓冲区

创建方式:ByteBuffer.allocate()

位置:非直接缓冲区存在于JVM的堆空间中,收到GC的管理

使用非直接缓冲区进行io操作时,需要将数据从JVM堆中复制到本地内存中,再进行io操作

2.直接缓冲区

创建方式:ByteBuffer.allocateDirect()

位置:直接缓冲区分配在本地内存中,不受GC的管理

在使用直接缓冲区进行IO操作时,直接在本地内存中进行。

异步文件通道 AsynchronousFileChannel

相关推荐
qq_3273427315 分钟前
Java实现离线身份证号码OCR识别
java·开发语言
沉默璇年36 分钟前
react中useMemo的使用场景
前端·react.js·前端框架
yqcoder42 分钟前
reactflow 中 useNodesState 模块作用
开发语言·前端·javascript
2401_882727571 小时前
BY组态-低代码web可视化组件
前端·后端·物联网·低代码·数学建模·前端框架
SoaringHeart1 小时前
Flutter进阶:基于 MLKit 的 OCR 文字识别
前端·flutter
Kkooe1 小时前
GitLab|数据迁移
运维·服务器·git
会发光的猪。1 小时前
css使用弹性盒,让每个子元素平均等分父元素的4/1大小
前端·javascript·vue.js
阿龟在奔跑1 小时前
引用类型的局部变量线程安全问题分析——以多线程对方法局部变量List类型对象实例的add、remove操作为例
java·jvm·安全·list
飞滕人生TYF2 小时前
m个数 生成n个数的所有组合 详解
java·递归
天下代码客2 小时前
【vue】vue中.sync修饰符如何使用--详细代码对比
前端·javascript·vue.js