Java NIO学习(一)NIO相关概念
Netty 与NIO
Netty在各种应用场合听过无数次了,对它的了解也仅局限于知道Netty是一个NIO的框架,可以用于开发分布式的Java程序。网络编程正好也是自己比较薄弱的环节,所以需要好好整理一下散落的知识点,为学习Netty打好基础,所以觉得有必要重新学习一下NIO。
BIO、NIO、AIO
按照以往的认知,NIO是Java4以后才出现的一种非阻塞式IO,这是与之前存在的BIO(Blocking-IO阻塞式IO)相比,字面上最直观的区别。除此之外,NIO最重要的特点是支持异步IO,所以也经常听人把NIO叫做异步IO(虽然不知道这么叫到底好不好。。。)
至于AIO,应该是Java7之后NIO的下一个形态,没用过,在这里就不多提啦。
阻塞&非阻塞
上面讲到了BIO和NIO在性能特点上的一个重要差异,这些概念网上都很好查,所以还是用自己的话说一下这几个概念的理解,也不知道能不能被看官所理解,如果有不恰当的地方希望能够指正。
大家都有去酒店吃饭的经历,吃饭的时候有的时候在大堂,有的时候在包厢。
区别在哪呢?在于服务员。
非阻塞式
如果在大堂吃饭,服务员给你递上菜单,你在点菜的这段时间里,服务员可能就去忙别的事了,比如给其他桌的客人递菜单,或者收拾其他桌客人离开后残留的垃圾。点菜的过程中服务员会时不时的回来问你点好了没有,如果没有,服务员就继续去忙别的事,过一会儿再回来问你。
阻塞式
如果在包厢吃饭,一般一个包厢都会配备一名专门的服务员,负责在席间斟酒和收拾餐盘里的垃圾,除非包厢内的饭局结束,否则这名服务员就一直在包厢内为客人服务。
上面例子里的服务员可以看做访问数据的进程,包厢里的客人是阻塞式的IO操作,因为他们还没吃完饭,缓冲区的数据(包厢资源)还没有被IO处理完毕(客人用餐),进程(服务员)的就必须一直等在那里。
而大厅的客人是非阻塞式的IO操作,缓冲区的数据(点餐服务)还没有被IO处理完毕(客人点餐),进程(服务员)可以得到一个反馈,自己可以先去忙别的事,等会再来也行。
NIO与IO
NIO和IO最大的区别是传输方式。
IO是以流的方式处理数据,而NIO是以块的方式处理数据。处理的数据量扩大带来了数据传输效率上的飞速提升,相对的,就没有流传输的简单性和直观性,代码复杂度也相应的提升了,这个会在之后的代码实例中有所体现。
说到这个,想起dubbo的底层是通过Socket(结合apache mina框架做底层调用)来建立长连接发送、接收数据。而mina的底层正是通过NIO实现的,所以,以块的方式处理数据在网络通信中是很普遍的,这个会在之后dubbo的学习中有所讲解。
下面进入正题,从三个基本要素来认识NIO:
- Buffer
- Channel
- Selector
Buffer缓冲区
直观的来说,Buffer你可以看做是一个数据存放容器,是我在上面说的一个个“数据块”,对应IO中的stream。
我们可以对其进行读写操作,Buffer实质上是一个数组,通常是一个字节数据,但也可以是其他类型的数组。但一个缓冲区不仅仅是一个数组,重要的是它提供了对数据的结构化访问。
常用的Buffer类型主要有:ByteBuffer、CharBuffer、IntBuffer、ShortBuffer、FloatBuffer、DoubleBuffer、LongBuffer。很好记,就是Java的八大基本类型除去Boolean。
下面以ByteBuffer为例,简单说一下常用方法:
声明缓冲区-allocate(int capacity)
//接收和发送的缓冲区
private ByteBuffer receiveBuffer = ByteBuffer.allocate(1024);
private ByteBuffer sendBuffer = ByteBuffer.allocate(1024);
清空缓冲区-clear()/compact()
//清空发生缓冲区,准备发送消息
sendBuffer.clear();
clear() 方法会清空整个缓冲区,compact() 方法只会清除已经读过的数据。任何未读的数据都被移到缓冲区的起始处,新写入的数据将放到缓冲区未读数据的后面。
写缓冲区-put(byte[] src)
String sendText = "player";
sendBuffer.put(sendText.getBytes());
读写指针操作-flip()
sendBuffer.flip();
我们看一下flip()方法的源码:
public final Buffer flip() {
limit = position;
position = 0;
mark = -1;
return this;
}
flip方法涉及到bufer中的Capacity、Position和Limit三个数值的操作,我们先看一下这三个数值分别代表什么意义。
- position:跟踪已经写了多少数据或读了多少数据,它指向的是下一个字节来自哪个位置
- limit:代表还有多少数据可以取出或还有多少空间可以写入,它的值小于等于capacity。
- capacity:代表缓冲区的最大容量,一般新建一个缓冲区的时候,limit的值和capacity的值默认是相等的。
其中Capacity在读写模式下都是固定的,就是我们分配的缓冲大小,Position类似于读写指针,表示当前读(写)到什么位置,Limit在写模式下表示最多能写入多少数据,此时和Capacity相同,在读模式下表示最多能读多少数据,此时和缓存中的实际数据大小相同。在写模式下调用flip方法,那么limit就设置为了position当前的值(即当前写了多少数据),position会被置为0,以表示读操作从缓存的头开始读。也就是说调用flip之后,读写指针指到缓存头部,并且设置了最多只能读出之前写入的数据长度(而不是整个缓存的容量大小)。
到此为止,我们把数据装填入了缓冲区Buffer,那么我们应该如何传输缓冲区的数据呢?接下来就要讲到通道的概念:Channel
Channel 通道
顾名思义,channel在NIO中扮演着通道的角色,负责传输缓冲区Buffer中的数据块。
打个比方,如果说Buffer是码头的话,码头中的一个个集装箱,就是存放在Buffer中的数据块,channel就是航道,运输这些集装箱,就需要走我们的航道,将集装箱送到目的地的码头,具体去哪个码头取集装箱,是由航道决定的,这条航道通往哪里,你就去哪个码头。
所以,我们获取集装箱(数据块)是去找码头(Buffer),找码头(Buffer)之前,我先需要通过航道(Channel)把数据块读入运入(读入)码头。
private ByteBuffer receiveBuffer = ByteBuffer.allocate(1024);
.
.
.
channel.read(receiveBuffer);
通过上面这个例子,可以引申出channel的一个特性:
- >对Channel的读写必须通过buffer对象
除此之外
- >Channel是双向的,可以读也可以写
//将缓冲区buffer写入通道
channel.write(sendBuffer);
最后就是,NIO既然是异步非阻塞的,channel自然也具有下面这个特征:
-
>Channel可以进行异步非阻塞的读写
当然,这一点需要我们手动设置
channel.configureBlocking(false);
有些类型的Channel必须设置成异步非阻塞模式,否则无法进行异步的IO工作,比如SocketChannel和ServerSocketChannel。但也有类型不具备异步,就像FileChannel。
常用的Channel类型有:
- FileChannel:从文件读取数据的
- DatagramChannel:读写UDP网络协议数据
- SocketChannel:读写TCP网络协议数据
- ServerSocketChannel:可以监听TCP连接
Selector选择器
接着上一章的那个比喻,如果我们把Buffer缓冲区看做码头,Channel通道看做航道的话,那么Selector选择器可以看做海上的一个调度中心,一个调度中心可以监听多个航道的事件,当然,这需要航道归属在这个调度中心的编制下。
创建一个Selector
Selector selector = Selector.open();
将Channel注册在某个Selector的管辖范围之下
Selector是一个对象,它可以被注册到很多个Channel上,监听各个Channel上发生的事件,并且能够根据事件情况决定Channel读写。这样,通过一个线程管理多个Channel,就可以处理大量网络连接了。
channel.register(selector, SelectionKey.OP_ACCEPT);
上面这行代码是有返回值的,这个方法会返回一个SelectionKey类型的结构。这个SelectionKey是NIO中事务处理的一个关键中间点,通过SelectionKey我们可以获取很多信息,可以说SelectionKey是整个NIO的业务大脑,关于SelectionKey,我将会放在下一章进行说明,毕竟自己目前的理解还不够深。
哈哈,阶段性…阶段性记录…
参考文章
感谢!
http://blog.csdn.net/suifeng3051/article/details/48160753
Java NIO 详解(一)http://walsh.iteye.com/blog/450114
java.nio.Buffer flip()方法jdk中文翻译错误
上一篇: quartz的一个demo
下一篇: 一个NIO例子