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

相关推荐
www_pp_11 分钟前
# 构建词汇表:自然语言处理中的关键步骤
前端·javascript·自然语言处理·easyui
24k小善33 分钟前
Flink TaskManager详解
java·大数据·flink·云计算
想不明白的过度思考者38 分钟前
Java从入门到“放弃”(精通)之旅——JavaSE终篇(异常)
java·开发语言
天天扭码1 小时前
总所周知,JavaScript中有很多函数定义方式,如何“因地制宜”?(ˉ﹃ˉ)
前端·javascript·面试
一个专注写代码的程序媛1 小时前
为什么vue的key值,不用index?
前端·javascript·vue.js
장숙혜1 小时前
ElementUi的Dropdown下拉菜单的详细介绍及使用
前端·javascript·vue.js
.生产的驴1 小时前
SpringBoot 封装统一API返回格式对象 标准化开发 请求封装 统一格式处理
java·数据库·spring boot·后端·spring·eclipse·maven
火柴盒zhang1 小时前
websheet之 编辑器
开发语言·前端·javascript·编辑器·spreadsheet·websheet
子非衣1 小时前
Windows云主机远程连接提示“出现了内部错误”
服务器·windows
某公司摸鱼前端1 小时前
uniapp 仿企微左边公司切换页
前端·uni-app·企业微信