文章目录

  • 二 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缓冲区的介绍 高并发相关推荐

  1. JAVA NIO:NIO与OIO的对比以及Channel通道、Selector选择器、Buffer缓冲区的介绍 //高并发

    文章目录 二 Java NIO (一)NIO对比OIO (二)概述三个核心组件 Channel通道 Selector选择器 Buffer缓冲区 (三)Buffer详解 1 Buffer类 2 四个属性 ...

  2. java基础巩固-宇宙第一AiYWM:为了维持生计,架构知识+分布式微服务+高并发高可用高性能知识序幕就此拉开(一:总览篇)~整起

    PART1:项目情景发展历程:很久很久以后,敏小言和老胡开的小超市,突然发生了一些变故: 超市中除了柜台格子业务之外,还有用户.视频.签到.评论.数据处理等业务[之前,咱们项目中的用户.视频.签到.评 ...

  3. java基础巩固-宇宙第一AiYWM:为了维持生计,架构知识+分布式微服务+高并发高可用高性能知识序幕就此拉开(三:注册中心、补充CAP定理、BASE 理论)~整起

    架构知识+分布式微服务+高并发高可用高性能知识序幕就此拉开(一:总览篇) 网关开了个头 你请求来了,我网关把你拦截住,验明正身,加以控制,协助你调用服务,完成请求的调用.但是这个过程中,为了解耦和或者 ...

  4. 高并发网络编程之NIO非阻塞网络编程

    文章目录 NIO非阻塞网络编程 Buffer缓冲区 Buffer工作原理: ByteBuffer内存类型 Channel通道 SocketChannel ServerSocketChannel Sel ...

  5. buffer java nio_Java NIO深入理解Buffer(缓冲区)

    前言 Github:https://github.com/yihonglei/java-all Project:java-nio 一 Buffer概述 Java NIO中的Buffer用于和NIO通道 ...

  6. java基础巩固-宇宙第一AiYWM:为了维持生计,四大基础之OS_Part_2整起~IO们那些事【包括五种IO模型:(BIO、NIO、IO多路复用、信号驱动、AIO);零拷贝、事件处理及并发等模型】

    PART0.前情提要: 通常用户进程的一个完整的IO分为两个阶段(IO有内存IO.网络IO和磁盘IO三种,通常我们说的IO指的是后两者!):[操作系统和驱动程序运行在内核空间,应用程序运行在用户空间, ...

  7. java io nio aio_Java IO、NIO、AIO知识总结

    本文主要讲述下自己对IO的理解,对IO的用法和细则可能没有顾虑到. 本文的理解基于以下几篇文章,他们对各自部分都讲的很细,对我理解IO提供了很大帮助. 该文主要讲解了Java IO的类体系以及他们各自 ...

  8. java中nio流_Java输入输出流IO介绍(与NIO比较)

    一.Java中流的类型 根据流的方向划分:输入流,输出流 根据流的传输单位:字节流,字符流 根据流的角色划分:节点流,处理流 节点流:直接连接数据源的流 处理流:通过构造方法接收一个节点流,对节点流使 ...

  9. Java NIO 编程:Buffer、Channel、Selector原理详解

    1 Java 中的 I/O模型:BIO.NIO.AIO 1.1 BIO.NIO.AIO概念介绍 I/O 模型简单的理解:就是 用什么样的通道进行数据的发送和接收,很大程度上决定了程序通信的性能. Ja ...

最新文章

  1. C++ 排序函数 sort(),qsort()的用法 附加.str()用法
  2. ESP8266之2M脚本
  3. linux下磁盘是硬盘吗,肿么确定linux系统上的硬盘哪个是主盘
  4. NLP中的Mask全解
  5. 计算机系统 过程调用
  6. 计算机or笔记本,笔记本or台式机?大学生第一个烦恼被它解决了
  7. python中Numpy中的替代值
  8. 微信小程序 - 实践- 001-实现一个多TAB的菜单框架
  9. ASP.NET MVC 4 内容映射
  10. 用子函数实现strlen.strcpy.strcat.strcmp(完整代码)
  11. class 'memcache' not found php,PHP Fatal error: Class 'Memcache' not found in
  12. 基于javaweb+jsp的大学生个人财务记账系统(带报告文档)
  13. 微信公众号接入百度天气API接口实现代码
  14. python矩阵计算器心得_NumPy入门攻略:手把手带你玩转这款强大的数据分析和计算工具...
  15. html一条竖线写法
  16. 天玑9200和a15对比 天玑9200和a15处理器哪个强
  17. root android oppo,OPPO R9S怎么ROOT oppor9s获取root权限的两种方法
  18. 【转】张飞眼中的真实三国-爆笑日记
  19. Python代码搜索并下载酷狗音乐
  20. overleaf 罕见中文字符 部分中文字符无法显示

热门文章

  1. win10安装wsl步骤
  2. win10系统桌面右键新建卡顿、反应慢问题
  3. 基于lxr的源代码浏览系统
  4. iOS复习中有关SDWebImage可能知识点总结
  5. Win10 BIOS改AHCI蓝屏无法启动的 两个解决方法
  6. 导入Spring源码找不到包spring-cglib-repack和spring-objenesis-repack
  7. finclip小程序运行机制与微信小程序运行机制
  8. WPS怎样设置多级标题(如四级标题)
  9. 下载keep运动软件_keep运动软件下载
  10. Kafka kafka-reassign-partitions.sh 命令使用