NIO中主要包括几大组件,selector、channel、buffer。selector后面介绍,channel则类似于BIO中的流,但是流的读取是单向的,例如只能读,或只能写,但是channel则是双向的。数据可以从channel读到buffer中,也可以从buffer中写入到channel中。

针对于客户端请求服务端的场景,NIO实现的结构图如下:

 禁止盗图,画了好久。。。

一、channel

channel主要包括以下几类

  • FileChannel-------------------------->从文件中读写数据
  • DatagramChannel------------------>通过UDP读写网络中的数据
  • SocketChannel----------------------->通过TCP读写网络中的数据
  • ServerSocketChannel--------------->监听新进来的TCP连接,并生成一个SocketChannel对象

基本示例:

//创建能访问任意位置的file文件
RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");
//获取fileChannel对象
FileChannel inChannel = aFile.getChannel();
//分配48字节大小的byteBuffer
ByteBuffer buf = ByteBuffer.allocate(48);
//读取channel中的数据到buffer中
int bytesRead = inChannel.read(buf); while (bytesRead != -1) {
System.out.println("Read " + bytesRead);
//将buffer从写模式切换到读模式
buf.flip();
while(buf.hasRemaining()){
System.out.print((char) buf.get());
}
  //清空整个缓冲区
buf.clear();
  //缓冲区已经全部读完,返回-1退出循环
bytesRead = inChannel.read(buf);
} aFile.close();

另外channel不仅可以读取数据到buffer中,当存在多个channel并且其中有一个channel为fileChannel时,channel之间可以互相传输数据

transferFrom() :可以将数据从源通道传输到FileChannel中

RandomAccessFile fromFile = new RandomAccessFile("fromFile.txt", "rw");
FileChannel fromChannel = fromFile.getChannel();
RandomAccessFile toFile = new RandomAccessFile("toFile.txt", "rw");
FileChannel toChannel = toFile.getChannel();
long position = 0;
long count = fromChannel.size();
toChannel.transferFrom(position, count, fromChannel);

transferTo()

RandomAccessFile fromFile = new RandomAccessFile("fromFile.txt", "rw");
FileChannel fromChannel = fromFile.getChannel();
RandomAccessFile toFile = new RandomAccessFile("toFile.txt", "rw");
FileChannel toChannel = toFile.getChannel();
long position = 0;
long count = fromChannel.size();
fromChannel.transferTo(position, count, toChannel);

二、Buffer

1、buffer主要包括如下几类:

  • ByteBuffer
  • CharBuffer
  • DoubleBuffer
  • FloatBuffer
  • IntBuffer
  • LongBuffer
  • ShortBuffer

2、使用buffer的一般步骤:

  • 写入数据到Buffer
  • 调用flip()方法
  • 从Buffer中读取数据
  • 调用clear()方法或者compact()方法------------>clear()方法会清空整个缓冲区,compact()只会清除已经读过的数据

3、buffer中的概念

为了理解Buffer的工作原理,需要熟悉它的三个属性:

  • capacity
  • position
  • limit

position和limit的含义取决于Buffer处在读模式还是写模式。不管Buffer处在什么模式,capacity的含义总是一样的。

capacity

作为一个内存块,Buffer有一个固定的大小值,也叫“capacity”.你只能往里写capacity个byte、long,char等类型。一旦Buffer满了,需要将其清空(通过读数据或者清除数据)才能继续写数据往里写数据。

position

当你写数据到Buffer中时,position表示当前的位置。初始的position值为0.当一个byte、long等数据写到Buffer后, position会向前移动到下一个可插入数据的Buffer单元。position最大可为capacity – 1.

当读取数据时,也是从某个特定位置读。当将Buffer从写模式切换到读模式,position会被重置为0. 当从Buffer的position处读取数据时,position向前移动到下一个可读的位置。

limit

在写模式下,Buffer的limit表示你最多能往Buffer里写多少数据。 写模式下,limit等于Buffer的capacity。

当切换Buffer到读模式时, limit表示你最多能读到多少数据。因此,当切换Buffer到读模式时,limit会被设置成写模式下的position值。换句话说,你能读到之前写入的所有数据(limit被设置成已写数据的数量,这个值在写模式下就是position)

4、常用方法

a、分配大小

//分配48字节大小的byteBuffer
ByteBuffer buf = ByteBuffer.allocate(48);
//分配2014字符大小的charBuffer
CharBuffer buf = CharBuffer.allocate(1024);

b、写数据到buffer中

包括两种方式:一是从channel中读数据到buffer中,另外一种就是调用buffer的put()方法

//一、channel读取数据到buffer
int bytesRead = inChannel.read(buf);
//二、调用channel的put()方法
buf.put(127);

c、读取数据

同样包括两种方式:一是写入数据到channel中,另外一种就是调用buffer的get()方法

//一、将数据写入到channel中
int bytesWritten = inChannel.write(buf);
//二、调用buffer的get()方法读取数据
byte aByte = buf.get();

d、读写模式切换

buffer.flip()

e、清除buffer

一旦读完Buffer中的数据,需要让Buffer准备好再次被写入。可以通过clear()或compact()方法来完成。

如果调用的是clear()方法,position将被设回0,limit被设置成 capacity的值。换句话说,Buffer 被清空了。Buffer中的数据并未清除,只是这些标记告诉我们可以从哪里开始往Buffer里写数据。

如果Buffer中有一些未读的数据,调用clear()方法,数据将“被遗忘”,意味着不再有任何标记会告诉你哪些数据被读过,哪些还没有。

如果Buffer中仍有未读的数据,且后续还需要这些数据,但是此时想要先先写些数据,那么使用compact()方法。

compact()方法将所有未读的数据拷贝到Buffer起始处。然后将position设到最后一个未读元素正后面。limit属性依然像clear()方法一样,设置成capacity。现在Buffer准备好写数据了,但是不会覆盖未读的数据。

另外NIO支持scatter/gather,说白了,就是一个channel可以读取数据到多个buffer中去,或者多个buffer可以写入到channel中。当第一个buffer写满之后,会紧接着读取到第二个buffer中去,如下图:

channel-->buffer

ByteBuffer header = ByteBuffer.allocate(128);
ByteBuffer body = ByteBuffer.allocate(1024);
ByteBuffer[] bufferArray = { header, body };
channel.read(bufferArray);

buffer-->channel

ByteBuffer header = ByteBuffer.allocate(128);
ByteBuffer body = ByteBuffer.allocate(1024);
ByteBuffer[] bufferArray = { header, body };
channel.write(bufferArray);

三、selector

为啥使用selector?在传统的BIO当中,监听每个客户端的请求都需要一个线程去处理,线程数的上升会涉及到大量的上下文切换的操作,这也是非常浪费性能的。NIO中基于事件驱动的理念,使用selector监听各种事件的发生,可以实现只开启一个线程

就可以管理所有的请求,当然实际情况合理的增加线程数可以大大提高性能。

注意:与Selector一起使用时,Channel必须处于非阻塞模式下。这意味着不能将FileChannel与Selector一起使用,因为FileChannel不能切换到非阻塞模式。

1、注册channel到selector中

channel.configureBlocking(false);
SelectionKey key = channel.register(selector,Selectionkey.OP_READ);

selectionKey用来描述事件,包括事件类型,以及对应的selector与channel等等。

第二个入参为事件类型,主要包括四种:

  • Connect
  • Accept
  • Read
  • Write

分别用常量表示为:

  • SelectionKey.OP_CONNECT
  • SelectionKey.OP_ACCEPT
  • SelectionKey.OP_READ
  • SelectionKey.OP_WRITE

当然selector监听channel时,可以对多个事件感兴趣,写法如下:

int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;

可能对selector、channel、事件三者的关系有点乱,用故事来总结一下:selector是父母,channel是孩子,父母监督孩子学习,孩子有很多同学,有些同学学习好,有些同学学习差,父母欢迎学习好的学生来家里玩,排斥成绩差的。那么特定的事件就可以理解

成那些成绩差的同学来家中,父母监听到了,开始行动,将他们赶走。

2、selectionKey

当channel注册到selector中时,会返回一个selectionKey对象,可以理解成事件的描述或是对注册的描述,主要包括这几个部分:

a、interest集合----------------------->感兴趣的事件集合。

int interestSet = selectionKey.interestOps();
//判断事件是否在集合中
boolean isInterestedInConnect = interestSet & SelectionKey.OP_CONNECT;

b、ready集合-------------------------->通道已经准备就绪的操作的集合

int readySet = selectionKey.readyOps();
//事件是否在已准备就绪的集合中,selectionKey提供了如下方法
selectionKey.isAcceptable();
selectionKey.isConnectable();
selectionKey.isReadable();
selectionKey.isWritable();

c、Channel

Channel  channel  = selectionKey.channel();

d、Selector

Selector selector = selectionKey.selector();

e、附加的对象(可选)

用户也可以将buffer等其他对象加到selectionKey上,方便后续操作

添加对象:

//添加对象到selectionKey有两种方式
selectionKey.attach(theObject);
SelectionKey key = channel.register(selector, SelectionKey.OP_READ, theObject);
//获取此附加对象
Object attachedObj = selectionKey.attachment();

3、select()

当把channel注册到selector中去之后,可以通过select()方法来监听对应channel的特定事件。主要有三种select方法:

  • int select()-------------------------------------->阻塞到被监听的channel至少有一个事件发生。
  • int select(long timeout)---------------------->在timeout(ms)的时间内阻塞,直到被监听的channel至少有一个事件发生。
  • int selectNow()--------------------------------->非阻塞,不管有没有事件发生,都立马返回。

4、selectedKeys()

Set selectedKeys = selector.selectedKeys();

完整的示例:

Selector selector = Selector.open();
channel.configureBlocking(false);
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
while(true) {
int readyChannels = selector.select();
if(readyChannels == 0) continue;
Set selectedKeys = selector.selectedKeys();
Iterator keyIterator = selectedKeys.iterator();
while(keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
if(key.isAcceptable()) {
// a connection was accepted by a ServerSocketChannel.
} else if (key.isConnectable()) {
// a connection was established with a remote server.
} else if (key.isReadable()) {
// a channel is ready for reading
} else if (key.isWritable()) {
// a channel is ready for writing
}
keyIterator.remove();
}
}

四、DatagramChannel

前面的示例都是基于TCP连接,现在讲述一下UDP的示例.

DatagramChannel是一个能收发UDP包的通道。因为UDP是无连接的网络协议,所以不能像其它通道那样读取和写入。它发送和接收的是数据包。

1、打开DatagramChannel

DatagramChannel channel = DatagramChannel.open();
channel.socket().bind(new InetSocketAddress(9999));

2、接收数据

ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
channel.receive(buf);

3、发送数据

String newData = "New String to write to file..." + System.currentTimeMillis();
ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
buf.put(newData.getBytes());
buf.flip();
int bytesSent = channel.send(buf, new InetSocketAddress("jenkov.com", 80));

NIO原理解析的更多相关文章

  1. java NIO 原理解析之学习笔记

    关键抽象 1.Buffer缓冲区 NIO数据传递模型,是一个连续的内存区域.所有数据传递均通过buffer类处理:NIO提供了字符串.整形.字节.堆等多种缓冲区. 2.Channel(通道) NIO把 ...

  2. 深入理解NIO(三)—— NIO原理及部分源码的解析

    深入理解NIO(三)—— NIO原理及部分源码的解析 欢迎回到淦™的源码看爆系列 在看完前面两个系列之后,相信大家对NIO也有了一定的理解,接下来我们就来深入源码去解读它,我这里的是OpenJDK-8 ...

  3. [原][Docker]特性与原理解析

    Docker特性与原理解析 文章假设你已经熟悉了Docker的基本命令和基本知识 首先看看Docker提供了哪些特性: 交互式Shell:Docker可以分配一个虚拟终端并关联到任何容器的标准输入上, ...

  4. 【算法】(查找你附近的人) GeoHash核心原理解析及代码实现

    本文地址 原文地址 分享提纲: 0. 引子 1. 感性认识GeoHash 2. GeoHash算法的步骤 3. GeoHash Base32编码长度与精度 4. GeoHash算法 5. 使用注意点( ...

  5. Web APi之过滤器执行过程原理解析【二】(十一)

    前言 上一节我们详细讲解了过滤器的创建过程以及粗略的介绍了五种过滤器,用此五种过滤器对实现对执行Action方法各个时期的拦截非常重要.这一节我们简单将讲述在Action方法上.控制器上.全局上以及授 ...

  6. Web APi之过滤器创建过程原理解析【一】(十)

    前言 Web API的简单流程就是从请求到执行到Action并最终作出响应,但是在这个过程有一把[筛子],那就是过滤器Filter,在从请求到Action这整个流程中使用Filter来进行相应的处理从 ...

  7. GeoHash原理解析

    GeoHash 核心原理解析       引子 一提到索引,大家脑子里马上浮现出B树索引,因为大量的数据库(如MySQL.oracle.PostgreSQL等)都在使用B树.B树索引本质上是对索引字段 ...

  8. alibaba-dexposed 原理解析

    alibaba-dexposed 原理解析 使用参考地址: http://blog.csdn.net/qxs965266509/article/details/49821413 原理参考地址: htt ...

  9. 支付宝Andfix 原理解析

    支付宝Andfix 原理解析 使用参考地址: http://blog.csdn.net/qxs965266509/article/details/49802429 原理参考地址: http://blo ...

随机推荐

  1. java虚拟机的原理

    所谓虚拟机,就是一台虚拟的机器.它是一款软件,用来执行一系列虚拟计算机指令,大体上虚拟机可以分为系统虚拟机和程序虚拟机,Visual Box .Vmare就属于系统虚拟机.他们完全是对物理计算机的仿真 ...

  2. eclipse Mars4.5.2安装fatjar

    试了在eclipse下添加plugins的方法,但是并没有生效 最后看了一篇博客@参考博客 原文转载: 首先声明,eclipse luna 和mars 楼主亲测可用. .安装Eclipse2.0版本的 ...

  3. Spring Boot 使用465端口发送邮件

    2017年10月27日 15:04:24 伊宇紫 阅读数:2710 标签: 465端口邮件springboot 更多 个人分类: Java   版权声明:本文为博主原创文章,未经博主允许不得转载. h ...

  4. 如何开发简单的javaweb项目,jsp+javabean+servlet

    一.相关的软件下载和环境配置 1.下载并配置JDK. 2.下载eclipse. 3.下载并配置apache-tomcat(服务器). 4.下载MySQL(数据库). 5.下载Navicat for M ...

  5. xcode10 - 打ipa上蒲公英或者fire.im

    1.选择空设备 2. 3. 4. 选择需要的 next 5. 6. 7. 8.选择位置 9. 选择ipa包 放到蒲公英 或者fire.im上 就行了

  6. Java 对象 引用,equal == string

    以前确实一直没注意这个概念,这次看了帖子才知道. 转载于:https://zwmf.iteye.com/blog/1738574 Java对象及其引用 关于对象与引用之间的一些基本概念. 初学Java ...

  7. 有关https有的网站可以访问有的访问不了的问题

    在开发中遇到这种情况,在开发工具里面访问可以,当时到了手机上之后就发现有的请求可以正常获取数据,有的则不行. 都是使用https地址,也配置后台了,但是就是不出数据,总是无法请求服务. 后来检查在手机 ...

  8. go语言处理文件上传和多个文件上传

    uploadOne.html代码如下: <!doctype html> <html lang="en"> <head> <meta cha ...

  9. Aspose.Words三 创建表格

    创建表格,实现合并行.和并列.表居中.表格水平和垂直居中.设置单元格边框颜色和样式. string templateFile = Server.MapPath("table_templ.do ...

  10. Web API、WCF和Web Service的区别

    [转载] Web Service 1.它是基于SOAP协议的,数据格式是XML 2.只支持HTTP协议 3.它不是开源的,但可以被任意一个了解XML的人使用 4.它只能部署在IIS上 WCF 1.这个 ...