Java NIO简介
1. 介绍
在1.4版本之前,Java的IO库的阻塞IO,简称OIO(Old IO);其后开始,就引入了新的异步IO,称为Java New IO类库,简称Java NIO;主要解决的问题就是同步阻塞的问题;IO模型介绍可参考https://mp.csdn.net/mp_blog/creation/editor/125082530
Java NIO有三个核心组件:
- Buffer(缓冲区)
- Channel(通道)
- Selector(选择器)
结合IO模型的介绍,Java NIO属于IO多路复用模型
1.1. Java NIO和OIO的对比
a. OIO是面向流的,NIO是面向缓冲区的;所以OIO是顺序读取,不能随意改变读取位置,而NIO是从缓冲区读数据,可以随意改变读取的位置
b. 一个是阻塞的,一个是非阻塞的;IO模型中有讲就不展开了
c. OIO没有选择器的概念,这个是最主要的差异原始点;NIO的实现是基于底层选择器的系统调用,需要操作系统提供支持,而OIO不需要选择器
1.2. 缓冲区
应用程序与通道的交互主要是读取和写入,NIO为了非阻塞读写,准备了此重要的组件Buffer;通道的读取是将数据从通道读取到缓冲区,通道的写入是将数据冲缓冲区写入到通道中;此特性是OIO所没有的,也是NIO非阻塞的重要前提之一;
1.3. 通道
在OIO中,一个网络连接会关联两个流:一个输入流,一个输出流(InputStream OutputStream)
在NIO中,使用通道的概念进行表示,即可以从通道中读取数据,也可以向通道写入数据
1.4. 选择器
选择器可以理解为是IO事件的监听和查询器,通过一个进程或者线程查询多个通道的IO事件的就绪状态;与OIO相比,NIO最大的优势是由于统一的监听和查询器,大大减小了系统开销,这种高效取决于Java Selector和操作系统IO多路复用的支持
理论扯差不多了,接下去扯一扯Java NIO中的Buffer
2. Buffer类
Buffer是一个抽象类,在java.nio包中,它就是一个内存块(由于数据类型不一致,内存块都定义在子类中)
具体实现有8种:
- ByteBuffer
- CharBuffer
- DoubleBuffer
- FloatBuffer
- IntBuffer
- LongBuffer
- ShortBuffer
- MappedByteBuffer
前7种覆盖了Java的基本数据类型;第8种是专门用于内存映射的
ByteBuffer是最最最常用的
2.1. Buffer类的重要属性
2.1.1. capacity属性
表示缓冲区的大小,即能写入数据的容量;此属性初始化后,就不能再进行修改,数组内存分配好后就不能再进行修改了
说通俗点,就是大家学java时,实例化一个数组的代码byte[] hb = new byte[1024];这个1024就是capacity,实例化后hb的大小就不能改了,除非重新初始化个新的数组,把hb的指针指向新的内存块;没很高深,大家都会
2.1.2. position属性
表示当前操作的位置,为什么叫操作的位置?因为和读写模式有关
写模式
> 刚进入写模式时(初始化allocate或者flip时),position值为0,从头开始写入
> 每当一个数据写到缓冲区之后,position会向后移动(nextPutIndex方法中,获取当前写入位置后,position+1)
> 当position到达limit时,缓冲区满,无法再写入,否则会报BufferOverflowException异常
读模式
> 刚进入读模式时(lip时),position值为0
> 从缓冲区读取数据后,position会向后移动(nextGetIndex方法中,获取当前读取位置后,position+1)
> 当position到达limit时,缓冲区无数据可读,否则会报BufferOverflowException异常
2.1.3. limit属性
表示可读取或者写入的数据最大上限
> 写模式下,limit会在初始化时被设置成缓冲区的capacity值,表示可以将缓冲区写满
> 读模式下,limit表示能从缓冲区中读多少数据
先写才能读,所以默认是写模式的,写完后flip翻转一下就是模式切换,这时limit值就会发生变化,flip时主要进行调整的也是position和limit两个属性,这种调整比较微妙(通俗点说就是大家有机会去写这个JavaNIO代码时,这是第一个大家容易写出bug的地方)
用一个例子来讲吧:
a. 先申请一个10长度的ByteBuffer
ByteBuffer byteBuffer = ByteBuffer.allocate(10);
此时,capacity=10,由于是新创建模式为写模式,position=0
b. 写入三个数据
byteBuffer.put((byte)1); byteBuffer.put((byte)2); byteBuffer.put((byte)3);
此时position=3
c. 数据读取
byteBuffer.flip();
此时,模式变为读模式,position=0,limit=3,表示从0开始读取数据,最多读三个
同时还有一个mark属性,用来临时记录position的位置,需要的时候再恢复至position上
属性 | 说明 |
capacity | 缓冲区最大容量,创建时指定,不可修改 |
limit | 结合读写模式和数据量和容量,表示当前的读写限制 |
position | 读写位置,缓冲区下一个被读写数组下标 |
mark | mark()方法标记mark=position,reset()方法恢复 |
2.2. Buffer类的重要方法
2.2.1. allocate()
用于创建一个固定大小的缓冲区对象,抽象方法定义在Buffer类中,在子类中具体实现
例如
ByteBuffer.allocate(10):创建一个ByteBuffer对象,分配内存空间10 * 1字节(new byte[10])
IntBuffer.allocate(10):创建一个IntBuffer对象,分配内存空间10 * 4字节(new int[10])
这个方法比较简单,核心的内容就是实例化一个对应的数组(学程序入门时创建数组的代码)
2.2.2. put()
在初始化完成后,即可通过put方法进行数据的写入
byteBuffer.put((byte)1);
2.2.3. flip()
数据写入后,是不能直接读取的,由于position,limit这些数据读写公用,所以需要进行模式切换去调整这些值,达到可读取的状态;所以flip就是这个作用进行模式翻转,转成读模式,以下是flip的源码
/*** Flips this buffer. The limit is set to the current position and then* the position is set to zero. If the mark is defined then it is* discarded.** <p> After a sequence of channel-read or <i>put</i> operations, invoke* this method to prepare for a sequence of channel-write or relative* <i>get</i> operations. For example:** <blockquote><pre>* buf.put(magic); // Prepend header* in.read(buf); // Read data into rest of buffer* buf.flip(); // Flip buffer* out.write(buf); // Write header + data to channel</pre></blockquote>** <p> This method is often used in conjunction with the {@link* java.nio.ByteBuffer#compact compact} method when transferring data from* one place to another. </p>** @return This buffer*/public final Buffer flip() {limit = position;position = 0;mark = -1;return this;}
然后问题来了,如果读完后是不是再flip一下变成写模式?
悲伤的告诉你,不是的
可以通过clear()或者compact()转换成写模式,上面的源码可以看出,flip时,第一个limit=position,flip时limit只小不大
顺便也提一下几个属性的大小顺序
mark <= position <= limit <= capacity
2.2.4. get()
这个方法也比较简单,当flip变成读模式时,就可以通过get()方法一个个读取数据了,每get一次,position指向下一个可读的下标
2.2.5. rewind()
这个方法也比较简单,你已经读完数据了,但是还想再读一遍?rewind一下
/*** Rewinds this buffer. The position is set to zero and the mark is* discarded.** <p> Invoke this method before a sequence of channel-write or <i>get</i>* operations, assuming that the limit has already been set* appropriately. For example:** <blockquote><pre>* out.write(buf); // Write remaining data* buf.rewind(); // Rewind buffer* buf.get(array); // Copy data into array</pre></blockquote>** @return This buffer*/public final Buffer rewind() {position = 0;mark = -1;return this;}
rewind和flip其实很像,只是rewind不动limit属性,而flip会修改limit,所以连续flip两次就凉凉了,因为limit=0了
2.2.6. mark()和reset()
就像游戏里的一些标记技能,标记英雄的位置,再手动触发使英雄回到标记的位置
当你读取到某个位置后,希望后续再从这个位置开始,就可以mark一下,当你希望的时候reset一下,再从这个位置开始读数据
2.2.7. clear()
在读模式下,调用clear()将缓冲区切换成写模式
> 将position=0
> 将limit=capacity
2.2.8. 综上所诉,常见的操作步骤如下
> allocate创建缓冲区
> put写入数据
> flip切换成读模式
> get读取数据
> clear切换成写模式,继续put写入
3. Channel类
一个通道可以表示一个底层的文件描述符(什么是文件描述符(file descriptor)这里就不展开了),例如文件、网络连接,Java NIO中通道比较细化,本文主要讲其中4个
- FileChannel:文件通道,用于文件读写
- SocketChannel:套接字通道,用于TCP连接数据读写
- ServerSocketChannel:服务器套接字通道,用于监听TCP请求连接
- DatagramChannel:数据报通道,用于UDP数据读写
3.1. FileChannel
FileChannel是专门用于操作文件的通道,可读写文件,其为阻塞模式,不能设置为非阻塞模式
3.1.1. 获取FileChannel
a. 通过文件输入流
FileInputStream fis = new FileInputStream(file);
FileChannel channel = fis.getChannel();
b. 通过文件输出流
FileOutputStream fos = new FileOutputStream(file);
FileChannel channel = fos.getChannel();
c. RandomAccessFile
RandomAccessFile raf = new RandomAccessFile("file.txt", "rw");
FileChannel channel = raf.getChannel();
3.1.2. 读取FileChannel
RandomAccessFile raf = new RandomAccessFile("file.txt", "rw");
FileChannel channel = raf.getChannel();
ByteBuffer buf = ByteBuffer.allocate(1024);
int length = -1;
while ((length = channel.read(buf)) != -1) {
}
3.1.3. 写入FileChannel
buf.flip();
int length = -1;
while ((length = channel.write(buf)) != 0) {
}
3.1.4. 关闭通道
通道使用完后需要将其关闭
channel.close()
3.1.5. 数据刷盘
操作系统出于性能考虑,不会实时刷盘,需要时,可自行调用fore方法进行即时刷盘
channel.force(true);
3.2. SocketChannel
针对网络连接有两个类,一个SocketChannel,一个ServerSocketChannel
ServerSocketChannel负责连接的监听,SocketChannel负责数据传输
ServerSocketChannel仅用于服务器,SocketChannel双端使用
两者都支持阻塞和非阻塞模式,通过configureBlocking进行设置
3.2.1. 获取SocketChannel
SocketChannel socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
socketChannel.connect(new InetSocketAddress("127.0.0.1", 80))
注意非阻塞模式下,connect方法不阻塞,但是有可能未连上服务器,需要自旋socketChannel.finishConnect()检测是否连接上服务器
3.2.2. 读取SocketChannel
和文件类似
socketChannel.read(buf)
3.2.3. 写入SocketChannel
和文件类似
socketChannel.write(buf)
3.2.4. 关闭SocketChannel
socketChannel.shutdownOutput();
IOUtil.closeQuietly(socketChannel);
3.3. DatagramChannel
用来处理UDP的数据传输
3.3.1. 获取DatagramChannel
DatagramChannel datagramChannel = DatagramChannel.open();
datagramChannel.configureBlocking(false);
datagramChannel.connect(new InetSocketAddress("127.0.0.1", 80))
3.2.2. 读取DatagramChannel
datagramChannel.receive(buf)
3.2.3. 写入DatagramChannel
datagramChannel.send(buf, new InetSocketAddress("127.0.0.1", 80))
3.2.4. 关闭DatagramChannel
datagramChannel.close();
4. Selector类
选择器是什么?
简而言之,选择器的使命是完成IO多路复用,进行通道的注册、监听、事件查询;通道和选择器之间的管理通过register的方式完成,Channel.register(selector, int ops)
可供选择器监控的通道IO事件类型包括以下4种:
- 可读:SelectionKey.OP_READ
- 可写:SelectionKey.OP_WRITE
- 连接:SelectionKey.OP_CONNECT
- 接收:SelectionKey.OP_ACCEPT
传递多种事件可通过按位或进行实现
SelectionKey.OP_READ | SelectionKey.OP_WRITE
4.1. SelectableChannel
并不是所有的通道都能被选择器监控和选择的,例如上面提到的FileChannel,只有继承了SelectableChannel的通道才可以
4.2. SelectionKey
通道和选择器之间的关系注册成功后,具体的选择工作可通过Selector的select方法来完成,选择器不停的轮训通道中的状态,并返回注册过的IO事件,SelectionKey就是那些被选择器选中的事件
// 实例化选择器
Selector selector = Selector.open();
// 获取通道
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
serverSocketChannel.bind(new InetSocketAddress(8080));// 注意:此部分仅样例代码,由于非阻塞模式,需要注意通道是否准备好等,此处不体现// 将通道注册到选择器上
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);// 然后轮询感兴趣的IO事件
while (selector.select() > 0) {Set selectedKeys = selector.selectedKeys();// 轮询处理对应的事件
}
其中select()方法有多个重载实现版本
- select():阻塞调用,至少有一个事件
- select(timeout):和select一样,只是增加了超时时间
- selectNow():非阻塞,不管有没有都会立刻返回
整个理论部分就先讲到这里,后面会补充一些代码样例,未完待续...
Java NIO简介相关推荐
- (一:NIO系列)JAVA NIO 简介
出处:JAVA NIO 简介 Java 中 New I/O类库 是由 Java 1.4 引进的异步 IO.由于之前老的I/O类库是阻塞I/O,New I/O类库的目标就是要让Java支持非阻塞I/O, ...
- JAVA NIO 简介 (netty源码死磕1.1)
[基础篇]netty 源码死磕1.1: JAVA NIO简介 1. JAVA NIO简介 Java 中 New I/O类库 是由 Java 1.4 引进的异步 IO.由于之前老的I/O类库是阻塞I/ ...
- JAVA NIO 简介(转)
1. 基本 概念 IO 是主存和外部设备 ( 硬盘.终端和网络等 ) 拷贝数据的过程. IO 是操作系统的底层功能实现,底层通过 I/O 指令进行完成. 所有语言运行时系统提供执行 I/O 较高级 ...
- 你对Java网络编程了解的如何?Java NIO 网络编程 | Netty前期知识(二)
本文主要讲解NIO的简介.NIO和传统阻塞I/O有什么区别.NIO模型和传统I/O模型之间的对比.以及围绕NIO的三大组件来讲解,理论代码相结合. 很喜欢一句话:"沉下去,再浮上来" ...
- Java NIO (图解+秒懂+史上最全)
文章很长,建议收藏起来,慢慢读! Java 高并发 发烧友社群:疯狂创客圈 奉上以下珍贵的学习资源: 免费赠送 经典图书:<Java高并发核心编程(卷1)> 面试必备 + 大厂必备 +涨薪 ...
- java nio doug_Java NIO简介
引子 自从 JDK 1.4以后,我们迎来了java.nio这个包.那这个包有什么奥妙和作用呢? 下面我们就来简单介绍一下. I/O简介 I/O或者输入/输出指的是计算机与外部世界或者一个程序与计算机的 ...
- JAVA NIO学习一:NIO简介、NIOIO的主要区别
在前面学习了IO之后,今天我们开始进入NIO学习环节,首先我们会NIO做一个简单的介绍,让大家认识NIO,然后会和IO进行一个对比认识进行区分.好了,下面我们就开始学习: 一.NIO简介 1.概述 从 ...
- Java NIO————NIO 简介
引言 Java NIO (New IO,或Non Blocking IO) 是从Java1.4 版本开始引入的一个新的 IO API,可以代替标准的Java IO API. NIO与原来的IO有同样的 ...
- 【Netty】NIO 简介 ( NIO 模型 | NIO 三大组件 | 选择器 Selector | 通道 Channel | 缓冲区 Buffer | NIO 组件分配 | 缓冲区示例 )
文章目录 I . NIO 模型 II . NIO 三大组件交互流程 III . NIO 缓冲区 IV . NIO 与 BIO 对比 V . NIO 线程分配 VI . 缓冲区 ( Buffer ) 示 ...
最新文章
- Markdown语法记录
- git stash 强制恢复_开发中必须要掌握的 Git 技巧
- etcd分布式之分布式通知与协调
- 面向对象的程序设计之原型模式
- iOS相关,过年回来电脑上的证书都失效了
- C++重载运算符小结与注意点
- mysql 天数减1_mysql 日期加减天数
- 【CSS】css控制模块到顶层或底层
- [独家放送]Unity2019更新规划速览,将有官方的可视化编程!
- 语言用pad流程图求和例题_易编玩初级课解析:如何用编程玩转流程图?
- Linux下的虚拟机安装
- Reflector.exe + ilDasm.exe + ilasm.exe 破解DundasWebChart(VS2005) 5.5 成功
- Windows下RabbitMQ安装及入门
- python数据存储系列教程——python对象与json字符串的相互转化,json文件的存储与读取
- 像Selenium爬网页一样爬手机App,可见即可爬——appium 教程(一)appium安装windows版
- VMware Error | IP地址经常变更
- 梁宁《产品思维》之18用户体验
- 什么是BLOB URL,为什么要使用它?
- python控制多个屏幕_使用python的多个屏幕
- python打开文件对话框