JAVA NIO:NIO与OIO的对比以及Channel通道、Selector选择器、Buffer缓冲区的介绍 高并发
文章目录
- 二 Java NIO
- (一)NIO对比OIO
- (二)概述三个核心组件
- Channel通道
- Selector选择器
- Buffer缓冲区
- (三)Buffer详解
- 1 Buffer类
- 2 四个属性
- 3 重要方法
- (四)Channel详解
- 1 FileChannel文件通道
- 2 SocketChannel套接字通道
- 3 DatagramChannel数据报通道
- (五)Selector选择器
- 选择器使用流程:
- 1、获取选择器示例
- 2.将通道注册到选择器
- 四种IO事件类型:
- SelectableChannel可选择通道
- validOps()
- SelectionKey选择键
- 3.选出感兴趣的IO就绪事件(选择键集合)
- Select()
- Select(long timeout)
- SelectNow()
- selectedKeys()
- 4.判断选择键IO就绪类型
- 其他
- selector的wakeup()
- SelectionKey的interestOps(int)
- 示例
博文所在专栏里有更多相关内容,如IO模型概述、Reactor反应器模式等,欢迎阅读与交流。
文字来源于读书笔记及个人心得,可能有引用其他博文,若引用了你的文字请联系我,我会加上来源,或者删除相关内容。
二 Java NIO
在1.4之前,Java IO类库是阻塞IO,从1.4开始,才引入了新的异步IO类库,即Java New IO,简称Java NIO。相对的,1.4之前称为Old IO,OIO。
(一)NIO对比OIO
NIO
OIO
面向缓冲区:只要从channel读取数据到buffer(读取),就可以读取buffer的任意位置。(写入:从buffer写到channel)
面向流:面向字节流或字符流,按顺序读取字节
非阻塞:内核缓冲区准备数据时,用户read操作都不会阻塞用户线程
阻塞:整个IO过程都会阻塞用户线程
有选择器概念:基于底层操作系统的选择器的系统调用,系统开销更小,因为不必为每个网络连接创建线程
无选择器概念
(二)概述三个核心组件
Channel通道
在Java NIO中,同一个网络连接(文件描述符)用一个通道表示,所有Java NIO的IO操作都从通道开始,即可从通道读取,也可向通道写入。
对比OIO,同个网络连接需要关联输入流和输出流共两个流。
Selector选择器
用于NIO通道的注册,以及查询就绪状态的IO事件。一个选择器可监控/管理多个通道,比起OIO系统开销更小,因为不必为每个网络连接创建线程。
Buffer缓冲区
本质是一个内存块(数组),用于应用程序和通道的交互,如通道的读取:将数据从通道读取到缓冲区;通道的写入:将数据从缓冲区写入到通道。
(三)Buffer详解
1 Buffer类
Buffer类是个抽象类,有8个子类:ByteBuffer、ShortBuffer、CharBuffer、IntBuffer、LongBuffer、FloatBuffer、DoubleBuffer、MappedByteBuffer,前7种覆盖了所有能在IO传输的Java基本数据类型,MappedByteBuffer专门用于内存映射。
2 四个属性
(1)capacity:容量,buffer内部byte[]数据内存块的容量,该容量不是该数组可写入的字节容量,而是可写入的对象数量,如DoubleBuffer写入的对象时double类型,capacity为10时表示可写入10个double数据。
(2)position:位置,缓冲区中下一个要被读/写的元素的索引。不同模式的position最大值不同:
写模式(通道-缓冲区):初始值0,最大可写值为limit-1,position=limit表示缓冲区已无位置可写;
读模式:初始值0,最大可读值为limit-1,position=limit表示缓冲区已无可读数(我看的书说的是,“读模式下最大可读值是limit“,但position=limit时已经没有数据可读了,所以我认为正确的说法是,读模式limit最大可取值是limit,但最大可读值是limit-1)
(3)limit:上限,缓冲区中当前的数据量
(4)mark:用于临时存储当前position
3 重要方法
(1)allocate()创建缓冲区:
IntBuffer ib=IntBuffer.allocate(5);
System.out.println("position:"+ib.position());
System.out.println("limit:"+ib.limit());
System.out.println("capacity:"+ib.capacity());
System.out.println("------");
缓冲区初创建时为写入模式。
(2)put()写入到缓冲区:
for(int i=0;i<5;i++){ib.put(i);
}
(3)flip()切换写模式为读模式:
设置limit为当前position、重置position为0、清除之前的mark标记
ib.flip();
(4)get()从缓冲区获取:
System.out.println(ib.get());
如果position=limit,即缓冲区数据读完了,此时若想进行写入,需调用clear()或compact()
(5)rewind()倒带重读:
重置position为0、清除之前的mark标记——与flip只差了对limit的处理,rewind不修改limit
ib.rewind();
System.out.println("-----after rewind-----");
System.out.println("position:"+ib.position());
System.out.println("limit:"+ib.limit());
System.out.println("capacity:"+ib.capacity());
(6)mark()和reset()
mark()表示暂存当前position值至mark属性,reset()将mark值恢复到position。
for(int i=0;ib.position()<ib.limit();i++){if(i==2) {ib.mark();System.out.println("mark");}if(i==4) {ib.reset();System.out.println("reset");}System.out.println(ib.get());System.out.println("position:"+ib.position());System.out.println("limit:"+ib.limit());System.out.println("capacity:"+ib.capacity());System.out.println("------");
}
(7)clear()清空缓冲区/compact()压缩缓冲区
在缓冲区处于读模式时调用clear或compact,缓冲区会切换为写模式,不同在于clear会重置position为0,而compact会将所有未读元素复制到缓冲区起始处,并将position设置为未读元素的后一位,如有一个维度元素,则compact后position为1。
(四)Channel详解
Java NIO中一个连接就是一个channel,也就是一个文件描述符。而对于不同的网络传输协议类型在java中有不同的NIO channel实现。这里着重聚焦于以下四种:
- FileChannel文件通道:用于文件的数据读写
- SocketChannel套接字通道:用于Socket套接字TCP连接
- ServerSocketChannel服务器嵌套字通道(或服务器监听通道):允许我们监听TCP请求并为监听到的请求创建一个SocketChannel套接字通道
- DatagramChannel数据报通道:用于UDP协议的数据读写
1 FileChannel文件通道
用于从文件读取数据或者向文件写入数据。只有阻塞模式,无法设置为非阻塞
(1)获取FileChannel通道:通过文件流获取,或通过文件随机访问类获取。文件流、文件随机访问类、通道,都需要手动关闭
RandomAccessFile infile=new RandomAccessFile("C:\Desktop\消息累加器.png","rw");
FileChannel inChannel=infile.getChannel();
RandomAccessFile outfile=new RandomAccessFile("C:\Desktop\消息累加器-copy.png","rw");
FileChannel outChannel=outfile.getChannel();
(2)read(buf)读取通道(对通道来说是读取,对缓冲区来说是写入)
ByteBuffer buffer=ByteBuffer.allocate(1024);
int i=-1;
while((i=inChannel.read(buffer))!=-1){//......
}
(3)write(buf)写入通道(对缓冲区来说是读取,需要切换为读模式)
while((i=inChannel.read(buffer))!=-1){//第一次切换:翻转buf,切换为读模式buffer.flip();int outLength=0;//将buf写入到输出通道while((outLength= outChannel.write(buffer))!=0);//第二次切换:清除buf,切换为写模式buffer.clear();
}
(4)force()强制刷新到磁盘
在调用该方法之前,出于性能原因,操作系统可能会将数据缓存在内存中,而该方法能将所有未写入的数据从通道刷新到磁盘中。
outChannel.force(true);
(5)close关闭通道
channel.close();
(6)使用FileChannel完成文件复制的实例
public void FileChannelTest(){FileInputStream fis=null;FileChannel inChannel=null;FileOutputStream fos=null;FileChannel outChannel=null;try {fis=new FileInputStream("C:\Users\Simon\Desktop\消息累加器.png");inChannel=fis.getChannel();fos=new FileOutputStream("C:\Users\Simon\Desktop\消息累加器-copy.png");outChannel=fos.getChannel();ByteBuffer buffer=ByteBuffer.allocate(1024);int i=-1;while((i=inChannel.read(buffer))!=-1){//第一次切换:翻转buf,切换为读模式buffer.flip();int outLength=0;//将buf写入到输出通道while((outLength= outChannel.write(buffer))!=0);//第二次切换:清除buf,切换为写模式buffer.clear();}//强制刷新到磁盘outChannel.force(true);} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}finally{IOUtils.close(outChannel);IOUtils.close(fos);IOUtils.close(inChannel);IOUtils.close(fis);}
}
2 SocketChannel套接字通道
Java NIO中涉及网络连接的两个通道:
- SocketChannel负责连接的数据传输,对应OIO的Socket类,应用于服务端和客户端
- ServerSocketChannel负责连接的监听,对应OIO的ServerSocket类,应用于服务端
这两种通道都支持阻塞和非阻塞两种模式,调用configureBlocking(false)设置为非阻塞模式,传参true设置为阻塞模式。
示例
//该示例为运行过
@Test
public void socketChannelTest(){try {//1.获得套接字传输通道SocketChannel socketChannel=SocketChannel.open();//设置为非阻塞模式socketChannel.configureBlocking(false);//连接目标服务端的ip和端口socketChannel.connect(new InetSocketAddress("127.0.0.1",80));//不停自旋,直到连接真正建立起来while (!socketChannel.finishConnect()){}//2.从套接字通道读取数据(需通道可读才行,通过Selector选择器判断是否可读)ByteBuffer byteBuffer=ByteBuffer.allocate(1024);//read方法是异步的。返回读取到的字节数,若返回-1表示对方已输出结束int bytesRead=socketChannel.read(byteBuffer);//3.写入套接字通道//写入通道需读取缓冲区,因此现将缓冲区置为读模式byteBuffer.flip();socketChannel.write(byteBuffer);//4.关闭套接字通道//如果当前套接字通道是用来写入数据给对方服务器的,建议调用以下方法终止输出,向对方发送一个输出结束标志(-1)socketChannel.shutdownOutput();//调用close()关闭连接IOUtils.close(socketChannel);} catch (IOException e) {e.printStackTrace();}
}
3 DatagramChannel数据报通道
用于处理UDP的数据传输,使用UDP时,只需要知道服务器的IP和端口就可以传输数据
//未运行过
public void datagramChannelTest(){try {//1.获取数据报通道DatagramChannel channel=DatagramChannel.open();//设置为非阻塞模式channel.configureBlocking(false);//如果要接收数据,需绑定一个数据报监听接口channel.bind(new InetSocketAddress(18080));//2.从数据报中读取数据,通过Selector判断通道是否可读//创建缓冲区ByteBuffer byteBuffer=ByteBuffer.allocate(1024);//从数据报通道读入,并写入缓冲区,返回数据发送端的连接地址(包括IP和端口)SocketAddress clientAddr=channel.receive(byteBuffer);//3.写入数据报通道byteBuffer.flip();channel.send(byteBuffer,new InetSocketAddress("127.0.0.1",18899));byteBuffer.clear();//4.关闭数据报通道IOUtils.close(channel);} catch (IOException e) {e.printStackTrace();}
}
(五)Selector选择器
简单说,选择器的使命是完成IO多路复用。
一般一个单线程处理一个选择器,一个选择器可监控多个通道的IO状况。
只有继承了抽象类SelectableChannel才能被选择/监控,例如FileChannel就不行。
选择器使用流程:
1、获取选择器示例
Selector selector=Selector.open();
2.将通道注册到选择器
channel.register(selector,SelectionKey.OP_ACCEPT);
channel.register(selector,SelectionKey.OP_ACCEPT|SelectionKey.OP_WRITE)
通道对象调用register方法完成在选择器上的注册,其中参数一是选择器对象,参数二是监听的IO事件类型(状态)。
监控同个通道的多个事件,使用“按位或”运算符“|”。
注册到选择器的通道必须是非阻塞模式,否则会抛出异常
四种IO事件类型:
- SelectionKey.OP_ACCEPT:接收就绪,某个Channel成功连接到另一个服务器
- SelectionKey.OP_CONNECT:连接就绪,某个ServerSocketChannel准备好接收新进入的连接
- SelectionKey.OP_READ,读就绪,一个有数据可读的通道
- SelectionKey.OP_WRITE:写就绪,等待写数据的通道
SelectableChannel可选择通道
只有继承了SelectableChannel抽象类的通道才能被选择器监控/选择
validOps()
一个通道不一定支持全部四种IO事件,
如ServerSocketChannel只支持OP_ACCEPT,而SocketChannel不支持OP_ACCEPT,
可通过channel.validOps()获取该通道支持的IO事件集合
SelectionKey选择键
即被选择器选中的指定状态下的通道。
3.选出感兴趣的IO就绪事件(选择键集合)
Select()
选择器的Select()方法可选出已注册且已就绪的IO事件,存储到selectedKeys
中,并返回被选中的通道的数量(通道数,不是事件数,准确说是上次select到这次select之间发生了就绪IO事件的通道数)。该方法为阻塞调用,会阻塞直到有已注册且已就绪的事件。
Select(long timeout)
与Select()类似,但指定了最长阻塞时间(毫秒数)
SelectNow()
与Select()类似,但非阻塞,不管有无就绪IO事件都立即返回
selectedKeys()
返回已注册且已就绪的选择键集合。
每个Selector内部都有两个SelectionKey集合(Set),一个是keys
,存储该选择器的所有选择键,一个是selectedKeys
,存储该选择器已注册且已就绪的选择键,通过选择键可以获得通道、通道的事件类型、选出该通道的选择键实例等。
这两个SelectionKey集合都是线程不安全的,都不可以直接手动添加元素进去,selectedKeys
可以执行remove操作,keys
不行。
4.判断选择键IO就绪类型
key.isAcceptable()、key.isConnectable()、key.isReadable()、key.isWritable()
其他
selector的wakeup()
用于唤醒阻塞的select()方法。
其他线程如果因为调用了selector.select()或者selector.select(long)这两个方法而阻塞,调用了selector.wakeup()之后,就会立即返回结果,并且返回的值!=0;
如果当前Selector没有阻塞在select方法上,那么本次 wakeup调用会在下一次select阻塞的时候生效。
SelectionKey的interestOps(int)
修改指定选择键的监听IO事件类型
示例
public void selectorTest(){try {//创建一个监听通道ServerSocketChannel channel=ServerSocketChannel.open();channel.configureBlocking(false);channel.bind(new InetSocketAddress("127.0.0.1",8091));//1.获取选择器实例Selector selector=Selector.open();//2.将通道注册到选择器//注册到选择器的通道必须是非阻塞模式,否则会抛出异常//一个通道不一定支持全部四种IO事件,如ServerSocketChannel只支持OP_ACCEPT,而SocketChannel不支持OP_ACCEPT//可通过channel.validOps()获取该通道支持的IO事件集合//监控同个通道的多个事件,使用“按位或”运算符“|”,如register(selector,SelectionKey.OP_ACCEPT|SelectionKey.OP_WRITE)channel.register(selector,SelectionKey.OP_ACCEPT);//3.选出感兴趣的IO就绪事件(选择键集合)while(selector.select()>0){//选择器集合不可添加元素,否则会报错Set<SelectionKey> selectionKeys=selector.selectedKeys();Iterator<SelectionKey> keyIterator=selectionKeys.iterator();while(keyIterator.hasNext()){SelectionKey key=keyIterator.next();//根据具体的IO事件类型,执行对应的业务操作if(key.isAcceptable()){//IO事件:ServerSocketChannel服务器监听通道有新连接//获取这个新连接(通道)SocketChannel socketChannel=channel.accept();socketChannel.configureBlocking(false);//若该通道为数据输入通道,可使用可读事件注册到选择器socketChannel.register(selector,SelectionKey.OP_READ);}else if(key.isConnectable()){//IO事件:传输通道连接成功}else if(key.isReadable()){//IO事件:传输通道可读//获取这个新连接(通道)SocketChannel socketChannel= (SocketChannel) key.channel();ByteBuffer byteBuffer=ByteBuffer.allocate(1024);int length=0;while((length=socketChannel.read(byteBuffer))>0){byteBuffer.flip();System.out.println(new String(byteBuffer.array(),0,length));byteBuffer.clear();}socketChannel.close();}else if(key.isWritable()){//IO事件:传输通道可写}//处理完成后,移除选择键,以免被下次循环重复处理keyIterator.remove();}}} catch (IOException e) {e.printStackTrace();}
}
JAVA NIO:NIO与OIO的对比以及Channel通道、Selector选择器、Buffer缓冲区的介绍 高并发相关推荐
- JAVA NIO:NIO与OIO的对比以及Channel通道、Selector选择器、Buffer缓冲区的介绍 //高并发
文章目录 二 Java NIO (一)NIO对比OIO (二)概述三个核心组件 Channel通道 Selector选择器 Buffer缓冲区 (三)Buffer详解 1 Buffer类 2 四个属性 ...
- java基础巩固-宇宙第一AiYWM:为了维持生计,架构知识+分布式微服务+高并发高可用高性能知识序幕就此拉开(一:总览篇)~整起
PART1:项目情景发展历程:很久很久以后,敏小言和老胡开的小超市,突然发生了一些变故: 超市中除了柜台格子业务之外,还有用户.视频.签到.评论.数据处理等业务[之前,咱们项目中的用户.视频.签到.评 ...
- java基础巩固-宇宙第一AiYWM:为了维持生计,架构知识+分布式微服务+高并发高可用高性能知识序幕就此拉开(三:注册中心、补充CAP定理、BASE 理论)~整起
架构知识+分布式微服务+高并发高可用高性能知识序幕就此拉开(一:总览篇) 网关开了个头 你请求来了,我网关把你拦截住,验明正身,加以控制,协助你调用服务,完成请求的调用.但是这个过程中,为了解耦和或者 ...
- 高并发网络编程之NIO非阻塞网络编程
文章目录 NIO非阻塞网络编程 Buffer缓冲区 Buffer工作原理: ByteBuffer内存类型 Channel通道 SocketChannel ServerSocketChannel Sel ...
- buffer java nio_Java NIO深入理解Buffer(缓冲区)
前言 Github:https://github.com/yihonglei/java-all Project:java-nio 一 Buffer概述 Java NIO中的Buffer用于和NIO通道 ...
- java基础巩固-宇宙第一AiYWM:为了维持生计,四大基础之OS_Part_2整起~IO们那些事【包括五种IO模型:(BIO、NIO、IO多路复用、信号驱动、AIO);零拷贝、事件处理及并发等模型】
PART0.前情提要: 通常用户进程的一个完整的IO分为两个阶段(IO有内存IO.网络IO和磁盘IO三种,通常我们说的IO指的是后两者!):[操作系统和驱动程序运行在内核空间,应用程序运行在用户空间, ...
- java io nio aio_Java IO、NIO、AIO知识总结
本文主要讲述下自己对IO的理解,对IO的用法和细则可能没有顾虑到. 本文的理解基于以下几篇文章,他们对各自部分都讲的很细,对我理解IO提供了很大帮助. 该文主要讲解了Java IO的类体系以及他们各自 ...
- java中nio流_Java输入输出流IO介绍(与NIO比较)
一.Java中流的类型 根据流的方向划分:输入流,输出流 根据流的传输单位:字节流,字符流 根据流的角色划分:节点流,处理流 节点流:直接连接数据源的流 处理流:通过构造方法接收一个节点流,对节点流使 ...
- Java NIO 编程:Buffer、Channel、Selector原理详解
1 Java 中的 I/O模型:BIO.NIO.AIO 1.1 BIO.NIO.AIO概念介绍 I/O 模型简单的理解:就是 用什么样的通道进行数据的发送和接收,很大程度上决定了程序通信的性能. Ja ...
最新文章
- C++ 排序函数 sort(),qsort()的用法 附加.str()用法
- ESP8266之2M脚本
- linux下磁盘是硬盘吗,肿么确定linux系统上的硬盘哪个是主盘
- NLP中的Mask全解
- 计算机系统 过程调用
- 计算机or笔记本,笔记本or台式机?大学生第一个烦恼被它解决了
- python中Numpy中的替代值
- 微信小程序 - 实践- 001-实现一个多TAB的菜单框架
- ASP.NET MVC 4 内容映射
- 用子函数实现strlen.strcpy.strcat.strcmp(完整代码)
- class 'memcache' not found php,PHP Fatal error: Class 'Memcache' not found in
- 基于javaweb+jsp的大学生个人财务记账系统(带报告文档)
- 微信公众号接入百度天气API接口实现代码
- python矩阵计算器心得_NumPy入门攻略:手把手带你玩转这款强大的数据分析和计算工具...
- html一条竖线写法
- 天玑9200和a15对比 天玑9200和a15处理器哪个强
- root android oppo,OPPO R9S怎么ROOT oppor9s获取root权限的两种方法
- 【转】张飞眼中的真实三国-爆笑日记
- Python代码搜索并下载酷狗音乐
- overleaf 罕见中文字符 部分中文字符无法显示