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

相关推荐
知识分享小能手1 分钟前
Java学习教程,从入门到精通,Java switch语句语法知识点(14)
java·开发语言·python·学习·javaee·大数据开发·java大数据
是程序喵呀3 分钟前
idea 创建java文件增加注释
java·ide·intellij-idea
花心蝴蝶.5 分钟前
Thread类及线程的核心操作
java·jvm·windows
苹果醋311 分钟前
springboot-springboot官方文档架构
java·运维·spring boot·mysql·nginx
每天都要喝奶茶19 分钟前
vue3uniapp实现自定义拱形底部导航栏,解决首次闪烁问题
前端·vue.js·uni-app
May_Xu_20 分钟前
vue3+less使用主题定制(多主题定制)可切换主题
前端·javascript·vue.js·vue·less·css3
qq_4275060820 分钟前
less解决function中return写法在浏览器被识别成Object导致样式失败的问题
前端·css·less
Elastic 中国社区官方博客26 分钟前
将你的 Kibana Dev Console 请求导出到 Python 和 JavaScript 代码
大数据·开发语言·前端·javascript·python·elasticsearch·ecmascript
葱白有滋味1 小时前
浏览器无法访问非80端口网页
运维·服务器·网络
北京_宏哥1 小时前
《最新出炉》系列入门篇-Python+Playwright自动化测试-41-录制视频
前端·python·测试