在之前的文章Netty专题-(1)初识Netty中提到了NIO三大核心Selector 、 Channel 和 Buffer,所以在这一章重点会是介绍这三个核心。

1 缓冲区(Buffer)

1.1 基本介绍

(1)缓冲区本质上是一个可以读写数据的内存块,可以理解成是一个容器对象(含数组);
(2)该对象提供了一组方法,可以更轻松地使用内存块;
(3)缓冲区对象内置了一些机制,能够跟踪和记录缓冲区的状态变化情况;
(4)Channel 提供从文件、网络读取数据的渠道,但是读取或写入的数据都必须经由 Buffer,如图:

1.2 Buffer类及其子类

(1)在 NIO 中,Buffer 是一个顶层父类,它是一个抽象类, 类的层级关系图:

(2)Buffer 类定义了所有的缓冲区都具有的四个属性来提供关于其所包含的数据元素的信息:

属性 描述
mark 标记
position 位置,下一个要被读或者写的元素的索引,每次读写缓存区数据时都会改变值,为下次读写做准备
limit 表示缓冲区的当前终点,不能对缓冲区超过极限的位置进行读写操作,且极限是可以修改的
capacity 容量,即可以容纳的最大数据量,在缓冲区创建时被设定并且不能改变

(3)Buffer 类相关方法一览

1.2 ByteBuffer
从前面可以看出对于 Java 中的基本数据类型(boolean 除外),都有一个 Buffer 类型与之相对应,最常用的是 ByteBuffer 类(二进制数据),该类的主要方法如下:

2 通道(Channel)

2.1 基本介绍

(1)NIO 的通道类似于流,但有些区别如下:

  • 通道可以同时进行读写,而流只能读或者只能写
  • 通道可以实现异步读写数据
  • 通道可以从缓冲读数据,也可以写数据到缓冲

(2)BIO 中的 stream 是单向的,例如 FileInputStream 对象只能进行读取数据的操作,而 NIO 中的通道(Channel) 是双向的,可以读操作,也可以写操作
(3)Channel 在 NIO 中是一个接口 public interface Channel extends Closeable{}

(4)常 用 的 Channel 类 有 : FileChannel 、 DatagramChannel 、 ServerSocketChannel 和 SocketChannel 。ServerSocketChanne 类似 ServerSocket , SocketChannel 类似 Socket
(5)FileChannel 用于文件的数据读写,DatagramChannel 用于 UDP 的数据读写,ServerSocketChannel 和 SocketChannel 用于 TCP 的数据读写。

2.2 FileChannel 类

2.2.1 FileChannel 类主要方法

FileChannel 主要用来对本地文件进行 IO 操作,常见的方法有:

  • public int read(ByteBuffer dst) ,从通道读取数据并放到缓冲区中
  • public int write(ByteBuffer src) ,把缓冲区的数据写到通道中
  • public long transferFrom(ReadableByteChannel src, long position, long count),从目标通道中复制数据到当前通道
  • public long transferTo(long position, long count, WritableByteChannel target),把数据从当前通道复制给目标通道

2.2.2 FileChannel 类应用实例

上面说了那么多可能大家只是停留在概念阶段,最终我们是需要落到实践,所以直接上干活,使用上面的两个核心类来进行相应的操作。
(1)使用ByteBuffer(缓冲) 和 FileChannel(通道)实现本地文件写数据,将 “hello,likangmin” 写入到 file01.txt 文件中,如果文件不存在就创建。代码里面有相应的注释,大家应该很容易就可以看懂,我这里就不做另外的补充。大家在看的同时希望大家自己动手实践一遍加深印象,只看不动手的话很快就会忘记。
代码如下,很简单的几句:

String str = "hello,likangmin";//创建一个输出流->channelFileOutputStream fileOutputStream = new FileOutputStream("D:\\file01.txt");//通过 fileOutputStream 获取 对应的 FileChannel//这个 fileChannel 真实 类型是  FileChannelImplFileChannel fileChannel = fileOutputStream.getChannel();//创建一个缓冲区 ByteBufferByteBuffer byteBuffer = ByteBuffer.allocate(1024);//将 str 放入 byteBufferbyteBuffer.put(str.getBytes());//对byteBuffer 进行flipbyteBuffer.flip();//将byteBuffer 数据写入到 fileChannelfileChannel.write(byteBuffer);fileOutputStream.close();

启动程序以后文件写入:

(2)使用ByteBuffer(缓冲) 和 FileChannel(通道)实现本地文件读数据,将刚刚创建的 file01.txt 中的数据读入到程序,并显示在控制台。
代码如下:

//创建文件的输入流File file = new File("d:\\file01.txt");FileInputStream fileInputStream = new FileInputStream(file);//通过fileInputStream 获取对应的FileChannel -> 实际类型  FileChannelImplFileChannel fileChannel = fileInputStream.getChannel();//创建缓冲区ByteBuffer byteBuffer = ByteBuffer.allocate((int) file.length());//将 通道的数据读入到BufferfileChannel.read(byteBuffer);//将byteBuffer 的 字节数据 转成StringSystem.out.println(new String(byteBuffer.array()));fileInputStream.close();

运行之后在控制台打印:

(3)在上面的例子中,我们需要单独使用两个Buffer来进行操作,那么可以使用一个Buffer来进行文件的读取和写入呢?当然是可以的,其主要的实现思路图如下,使用 FileChannel(通道) 和 方法 read , write,完成文件从1.txt到2.txt的拷贝。

代码如下:

        FileInputStream fileInputStream = new FileInputStream("1.txt");FileChannel fileChannel01 = fileInputStream.getChannel();FileOutputStream fileOutputStream = new FileOutputStream("2.txt");FileChannel fileChannel02 = fileOutputStream.getChannel();ByteBuffer byteBuffer = ByteBuffer.allocate(512);while (true) { //循环读取//这里有一个重要的操作,一定不要忘了/*public final Buffer clear() {position = 0;limit = capacity;mark = -1;return this;}*/byteBuffer.clear(); //清空bufferint read = fileChannel01.read(byteBuffer);System.out.println("read =" + read);if(read == -1) { //表示读完break;}//将buffer 中的数据写入到 fileChannel02 -- 2.txtbyteBuffer.flip();fileChannel02.write(byteBuffer);}//关闭相关的流fileInputStream.close();fileOutputStream.close();}

文件1.txt的内容:

在开始之前2.txt不存在,启动程序以后,2.txt出现,里面的内容与1.txt完全一致。

(4)在例(3)中,使用Buffer完成文件的拷贝,实际上我们呢可以直接使用transferFrom方法完成拷贝,比如我们拷贝以下的这张照片。

代码如下:

        //创建相关流FileInputStream fileInputStream = new FileInputStream("a.jpg");FileOutputStream fileOutputStream = new FileOutputStream("a2.jpg");//获取各个流对应的filechannelFileChannel sourceCh = fileInputStream.getChannel();FileChannel destCh = fileOutputStream.getChannel();//使用transferForm完成拷贝destCh.transferFrom(sourceCh,0,sourceCh.size());//关闭相关通道和流sourceCh.close();destCh.close();fileInputStream.close();fileOutputStream.close();

运行完程序以后,出现a2.jpg:

2.2.3 Buffer 和 Channel 的注意事项和细节

(1)ByteBuffer 支持类型化的 put 和 get, put 放入的是什么数据类型,get 就应该使用相应的数据类型来取出,否 则可能有 BufferUnderflowException 异常。如下:

如果没有按照对应的数据类型进行获取,则会出现异常。

(2)可以将一个普通 Buffer 转成只读 Buffer

运行程序:

(3)NIO 还提供了 MappedByteBuffer, 可以让文件直接在内存(堆外的内存)中进行修改, 而如何同步到文件 由 NIO 来完成

这里对以下主要的地方做一下说明:

 MappedByteBuffer mappedByteBuffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, 5);
参数1: FileChannel.MapMode.READ_WRITE 使用的读写模式
参数2: 0 : 可以直接修改的起始位置
参数3:  5: 是映射到内存的大小(不是索引位置) ,即将 1.txt 的多少个字节映射到内存
可以直接修改的范围就是 0-5
实际类型 DirectByteBuffe

所以上面使用mappedByteBuffer.put(5, (byte) ‘Y’)会报IndexOutOfBoundsException异常:

(4)前面讲的读写操作,都是通过一个 Buffer 完成的,NIO 还支持 通过多个 Buffer (即 Buffer 数组) 完成读 写操作,即 Scattering 和 Gathering

Scattering:将数据写入到buffer时,可以采用buffer数组,依次写入  [分散]
Gathering: 从buffer读取数据时,可以采用buffer数组,依次读

代码如下,这里我们首次使用了ServerSocketChannel,大家不用考虑太多,在后面也会具体介绍,这里当前了解即可。

 //使用 ServerSocketChannel 和 SocketChannel 网络ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();InetSocketAddress inetSocketAddress = new InetSocketAddress(7000);//绑定端口到socket ,并启动serverSocketChannel.socket().bind(inetSocketAddress);//创建buffer数组ByteBuffer[] byteBuffers = new ByteBuffer[2];byteBuffers[0] = ByteBuffer.allocate(5);byteBuffers[1] = ByteBuffer.allocate(3);//等客户端连接(telnet)SocketChannel socketChannel = serverSocketChannel.accept();int messageLength = 8;   //假定从客户端接收8个字节//循环的读取while (true) {int byteRead = 0;while (byteRead < messageLength ) {long l = socketChannel.read(byteBuffers);byteRead += l; //累计读取的字节数System.out.println("byteRead=" + byteRead);//使用流打印, 看看当前的这个buffer的position 和 limitArrays.asList(byteBuffers).stream().map(buffer -> "postion=" + buffer.position() + ", limit=" + buffer.limit()).forEach(System.out::println);}//将所有的buffer进行flipArrays.asList(byteBuffers).forEach(buffer -> buffer.flip());//将数据读出显示到客户端long byteWirte = 0;while (byteWirte < messageLength) {long l = socketChannel.write(byteBuffers); //byteWirte += l;}//将所有的buffer 进行clearArrays.asList(byteBuffers).forEach(buffer-> {buffer.clear();});System.out.println("byteRead:=" + byteRead + " byteWrite=" + byteWirte + ", messagelength" + messageLength);}

用样使用telnet进行数据的发送,发现分了三次进行接收:

3 Selector(选择器)

3.1 基本介绍

(1)Java 的 NIO,用非阻塞的 IO 方式。可以用一个线程,处理多个的客户端连接,就会使用到 Selector(选择器)
(2)Selector 能够检测多个注册的通道上是否有事件发生(注意:多个 Channel 以事件的方式可以注册到同一个 Selector),如果有事件发生,便获取事件然后针对每个事件进行相应的处理。这样就可以只用一个单线程去管理多个通道,也就是管理多个连接和请求
(3)只有在连接通道真正有读写事件发生时,才会进行读写,就大大地减少了系统开销,并且不必为每个连接都创建一个线程,不用去维护多个线程
(4)避免了多线程之间的上下文切换导致的开销

3.2 Selector 示意图和特点说明


(1)Netty 的 IO 线程 NioEventLoop 聚合了 Selector(选择器,也叫多路复用器),可以同时并发处理成百上千个客 户端连接
(2)当线程从某客户端 Socket 通道进行读写数据时,若没有数据可用时,该线程可以进行其他任务
(3)线程通常将非阻塞 IO 的空闲时间用于在其他通道上执行 IO 操作,所以单独的线程可以管理多个输入和输出通道
(4)由于读写操作都是非阻塞的,这就可以充分提升 IO 线程的运行效率,避免由于频繁 I/O 阻塞导致的线程挂起
(5)一个 I/O 线程可以并发处理 N 个客户端连接和读写操作,这从根本上解决了传统同步阻塞 I/O 一连接一线程模型,架构的性能、弹性伸缩能力和可靠性都得到了极大的提升

3.3 Selector 类相关方法

Selector 类是一个抽象类, 常用方法和说明如下:

3.4 注意事项

(1)NIO 中的 ServerSocketChannel 功能类似 ServerSocket,SocketChannel 功能类似 Socket
(2)selector 相关方法说明

  • selector.select()//阻塞
  • selector.select(1000);//阻塞 1000 毫秒,在 1000 毫秒后返回
  • selector.wakeup();//唤醒 selector
  • selector.selectNow();//不阻塞,立马返还

在这一章中我们介绍了NIO的三大核心类,包括类的定义和概念,并做了相应的示例。下一章我们将利用NIO的三大核心实现NIO的网路编程Netty专题-(3)NIO网络编程。

猜你感兴趣
Netty专题-(1)初识Netty
Netty专题-(2)NIO三大核心
Netty专题-(3)NIO网络编程
相关专题持续更新中,敬请期待…

更多文章请点击:更多…

Netty专题-(2)NIO三大核心相关推荐

  1. Java NIO三大核心之缓冲区Buffer概述

    三大核心原理示意图  说明: 每个 Channel 都会对应一个 Buffer: Selector 对应一个线程,一个线程对应多个 Channel(连接): 该图反应了有三个 Channel 注册到该 ...

  2. Netty专题-(3)NIO网络编程

    之前在上一章Netty专题-(2)NIO三大核心中介绍了NIO的三大核心类Selector . Channel 和 Buffer,这一章我们将利用这些核心进行编程实现相关的一些功能.在正式进入编程之前 ...

  3. java NIO模型和三大核心原理

    1.NIO (1)基本介绍 1)Java NIO全程 java non-blocking IO,是指JDK提供的新API.从JDK1.4开始,Java提供了一系列改进的输入/输出的新特性,被统称为NI ...

  4. Netty专题-(1)初识Netty

    写在前面 最近对Netty这一块进行了一下学习,因为之前不是很了解.这里在学习的过程中也跟着做了一些笔记,特意整理在这里,希望对想学习的小伙伴有一定的帮助.这里是从最开始的基础进行学习,所以对于想入门 ...

  5. 【Netty】NIO 简介 ( NIO 模型 | NIO 三大组件 | 选择器 Selector | 通道 Channel | 缓冲区 Buffer | NIO 组件分配 | 缓冲区示例 )

    文章目录 I . NIO 模型 II . NIO 三大组件交互流程 III . NIO 缓冲区 IV . NIO 与 BIO 对比 V . NIO 线程分配 VI . 缓冲区 ( Buffer ) 示 ...

  6. Netty大战之NIO

    Netty的介绍 官方的文档:尚硅谷netty文档 · 语雀 1.Netty 是一个异步的.基于事件驱动的网络应用框架,用以快速开发高性能.高可 靠性的网络 IO 程序. 2.Netty主要针对在TC ...

  7. netty系列之:NIO和netty详解

    文章目录 简介 NIO常用用法 NIO和EventLoopGroup NioEventLoopGroup SelectorProvider SelectStrategyFactory Rejected ...

  8. 腾讯AI Lab发布三大核心战略,与自然科研达成战略合作

    来源:腾讯AI实验室 3月15日,腾讯AI Lab第二届学术论坛在深圳举行,聚焦人工智能在医疗.游戏.多媒体内容.人机交互等四大领域的跨界研究与应用.全球30位顶级AI专家出席,对多项前沿研究成果进行 ...

  9. 汤晓鸥谈深度学习三大核心要素:算法设计、高性能的计算能力以及大数据

    汤晓鸥谈深度学习三大核心要素:算法设计.高性能的计算能力以及大数据 2017-05-21 15:02:28    深度学习    0 0 0 昨日(5月20日),香港中文大学汤晓鸥教授莅临 2017C ...

最新文章

  1. 将图片的二进制字节 在HTML页面中显示
  2. linux远程登录x11,windows linux 通过SSH X11Forwrding 使用图形化界面
  3. Android客户端与服务器端交互,如何保持session回话
  4. Mysql(五) JDBC
  5. Microsoft 顺序分析和聚类分析算法
  6. gitlab 构建tag_Gitlab详细操作
  7. 数据结构题(莫队算法)
  8. [react] create-react-app有什么好处?
  9. 让你的Android程序更省电
  10. OSX上摆脱vagrant搭建CoreOS集群
  11. dmf nmf_SQL Server 2019 –新的DMF sys.dm_db_page_info
  12. struts2文件上传类型的过滤
  13. Tomacat7启动报错 org.apache.catalina.deploy.WebXml addFilter
  14. MATLAB生成(对角线上元素相同的)三对角矩阵的实例
  15. python手机桌面开发_将Android手机打造成你的Python开发者桌面#华为云·寻找黑马程序员#...
  16. 微信分享返回 错误码 -6 的情况!
  17. linux安装xbox无线手柄,想不到xbox手柄配对方法居然还有3个【详解】
  18. Flurry使用初级教程
  19. 机器视觉系统——光源及打光
  20. 梯形公式和辛普森的Matlab

热门文章

  1. android/linux中的第一个init程序的启动
  2. [私]-optee的同步方法
  3. 题目3:文本文件单词的检索与计数(实现代码)
  4. CmsEasy 漏洞挖掘
  5. 逆向去除winrar广告
  6. 14、HTML <input>标签
  7. ppt的一些基础操作
  8. YAML 在Python中的配置应用
  9. 锁用不好,可能把自己锁住哦!(解决多线程的死锁问题)
  10. Cookie对象知识总结