初学nio,之前很多文章都看不懂,最近终于开窍了一点,赶紧记录下。本文适合小白,也欢迎各位大佬批评指正哟。
一、推荐教程
推荐并发编程网的nio译文教程:
(1)概述:http://ifeve.com/overview/
(2)channels:http://ifeve.com/channels/
(3)buffers:http://ifeve.com/buffers/
(4)scatter/Gather:http://ifeve.com/java-nio-scattergather/
(5)数据传输:http://ifeve.com/java-nio-channel-to-channel/
(6)selector:http://ifeve.com/selectors/
(7)FileChannel:http://ifeve.com/file-channel/
(8)ScoketChannel:http://ifeve.com/socket-channel/
(9)ServerSocketChannel:http://ifeve.com/server-socket-channel/
(10)DatagramChannel:http://ifeve.com/datagram-channel/
(11)Pipe:http://ifeve.com/pipe/
(12)IO NIO对比:http://ifeve.com/java-nio-vs-io/
(13)NIO path:http://ifeve.com/java-nio-path/
(14)AsynchronousFileChannel:http://ifeve.com/java-nio-asynchronousfilechannel/
原文教程:http://tutorials.jenkov.com/java-nio/index.html
二、nio概述
要学习一项技术之前,首先来思考三个问题,是什么?为什么?怎么用?
(1)nio是什么?
non-blocking io,非阻塞的io。其实java的nio是同步非阻塞io。那么什么是阻塞io和非阻塞io呢?什么是同步io和异步io呢?
比如说已知要读取10个字节的数据,但是由于某些原因只能发送5个字节过来。
io模式 | 获取数据过程 |
阻塞io | 收到5个字节数据,但是我知道一共有10个字节的数据,就一定要等另外5个字节来了再一起返回。 |
非阻塞io | 只收到5个字节数据,那我就只返回5个字节吧,剩下的下次再来拿。 |
同步io | java自己进行处理 |
异步io | java委托操作系统进行处理,等到系统处理完了,会调用java的回调函数进行通知 |
关于阻塞io和非阻塞io可以参考 https://blog.csdn.net/qq_34638435/article/details/81878301。
显而易见,非阻塞io很耗时,如果数据迟迟没有准备出来就会一直等待。但是每次拿到的数据是完整的。非阻塞io返回快,但是数据可能不完整。
由于java的bio和nio都是同步的io,此处不过多展开,等待后面学习aio时在进行展开比较。
(2)为什么要用nio?使用场景有哪些?
看了上面的例子之后,你可能会觉得使用nio是因为它快,但事实并非如此,使用nio的主要原因是为了节约服务器的资源。
比如你要开发一个网站,使用阻塞io的话,对于客户端的一个请求A,你需要分配给他一个线程A。由于它只会等数据准备完成再返回,你的线程A只能服务于客户请求A。这时候来了请求B,即使线程A正在等待数据,什么也没干,也不能使用线程A了,只能再开个线程B给请求B使用,这就是一请求一线程的情况。如果请求量大了,服务端的线程池就会耗尽。同时线程间的切换需要消耗服务器的内存和cpu资源。
当然还有其他我还不太能理解的原因,请参考:https://www.cnblogs.com/wuyida/archive/2012/12/29/6301060.html
下面就进入了如何使用nio的环节。
三、nio核心组件
推荐教程里面讲了很多东西,其实nio的核心只有这三个Buffer,Channel,Selector。
(1)Channel
字面意思就是通道,通道的一端是缓存(Buffer),另外一端可以是文件,网络套接字,或者是TCP,UDP的网络连接。我们可以类比IO中的stream,区别是stream是单向的,比如InputStream只能输入,OutputStream只能输出,而Channel是双向的。
针对数据来源的不同,Channel有多种实现:
Channel的实现 | 描述 |
FileChannel | 从文件获取数据 |
DatagramChannel | 通过UDP获取网络中的数据 |
SocketChannel | 通过TCP获取网络中的数据 |
SocketServerChannel | 可以监听新进的TCP连接的通道,类似SocketServer |
常见函数:
函数 | 解释 | 数据流 |
channel.write(buffer) | channel中的数据写入buffer | buffer ---> channel |
channel.read(buffer) | 从buffer中读取数据到channel | channel ---> buffer |
我个人之前是分不清channel的read和write函数的,这里想个办法来区分这两个方法的数据走向。首先联想我们自己,读和写指的是什么?读就是外部信息输入到大脑中,写就是大脑中的信息输出到外界。类比如下:
大脑 ---> CPU
外围组件 ---> channel
类比 | 读操作信息流 | 写操作数据流 |
大脑 | 外部信息 ---> 大脑 | 大脑 ---> 外部 |
CPU | channel ---> CPU | CPU ---> channel |
buffer相比磁盘等外部来说,属于内部。
(2)Buffer
就是一块被包装的内存,可以从channel获取数据,也可以把数据写入channel。针对不同的java类型有不同的包装类,如ByteBuffer,CharBuffer,ShortBuffer,IntBuffer,LongBuffer,FloatBuffer,DoubleBuffer。还有操作大文件的MappedByteBuffer。
buffer中常见字段:capacity,position,limit
比较好理解,capacity为容量,创建buffer的大小。读取buffer中数据时,position为可读数据的开头,limit为数据的结尾;向buffer写入数据时,position初始为0,写入一定数据后,position向后移位。
常见函数
常见函数 | 解释 |
buffer.flip() | 写模式转为读模式的翻转 |
buffer.clear() | 清空buffer,position置为0 |
buffer.compact() | 清除读过的数据,position置为未读的位置 |
buffer.rewind() | 重新读数据,position置为0 |
个人总是记不住flip是什么模式转为什么模式,记忆方法如下:
flip这个词是翻转的意思,当你创建一个buffer的时候,他一定是空的,所以开始是处于读模式的,模式翻转就变成了写模式。因此flip是读模式转为写模式。
(3)Selector
我们可以把selector比喻成快餐店的前台店员,每个顾客和的厨师可以类比为channel,排队的顾客向selector注册了点餐事件,当点完餐后顾客向selector注册了取餐事件,厨师向selector注册了做饭事件。selector处理了排队的点餐顾客后,会通知后台制作的店员,当轮训时发现厨师做好了饭,会通知等餐的顾客。可以抽象为下图:即一个selector可以处理多个channel,channel向selector注册感兴趣的事件,selector会轮训这些事件,当某事件准备好时,会通知对应的channel。
这样原本一请求一线程的模式可以由一个线程来完成,节省了服务器资源。
四、完整代码解析
待完成。
参考文章:
(1)推荐教程的文章
(2)一文让你彻底理解 Java NIO 核心组件: https://segmentfault.com/a/1190000017040893
(3)阻塞IO与非阻塞IO: https://blog.csdn.net/qq_34638435/article/details/81878301
(4)NIO:为什么需要 NIO?:https://www.cnblogs.com/wuyida/archive/2012/12/29/6301060.html