提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
NIO在jdk1.4时引进,NIO2在jdk1.7时引进。
一、面向流与面向缓冲区
传统的IO是面向流(Stream)的。
流相当于水流,类似于自来水厂的水管, 水管里面装满着着数据的二进制流。
每一种流都是单向的【缺少重用】
NIO不使用流,而使用通道进行传输。
通道可以比作一条铁路,光有铁路并不能拉货,还需要火车,而缓冲区就是通道上的火车。
缓冲区可以一次存储一组数据,由通道负责源和目的地的传输。
通道是双向【注:双向不是说就可以全双工了,而是一种半双工的,每次读写之间需要进行切换,内部使用几个指针位置的改变来实现。】
因为实际运输的是缓冲区而不是通道,因此NIO称为面向缓冲区
二、缓冲区
boolean表示很淦,真把阴阳人不当人呗
核心属性:
capacity:容量,声名之后不可改变
limit:划定界限,limit之后的数据不能读写【如,读数据时,只有部分区域有内容,就需要设置limit为当前缓冲区含有数据的最后一个单元的后一个位置】
position:正在操作的数据【如,读数据时,最左边为空的单元】
remark() 记录当前位置postion,通过reset()返回这个位置
核心API
get()从缓冲区读取数据
put()写数据到缓冲区
flip() 切换读取模式
rewind() 重复读
clear() 清空缓冲区,但是数据还在,数据处于被遗忘状态【指针归零】。注意:被遗忘但是还是可以读,只是limit,position这些属性无法使用了。
hasRemain() 与 remaining() 是否有存货和得到一个存货。
直接缓冲区与非直接缓冲区
直接缓冲区是指,使用本地方法申请操作系统的系统调用,并为NIO分配物理内存作为缓冲区。
申请直接缓冲区使用allocateDirect()方法。
非直接缓冲区:
从JVM堆区分配内存作为缓冲区。
非直接缓冲的读取需要建立堆内存到操作系统内核管理的内存之间的映射,这种映射依靠复制来实现,因此每次读写要多一次无意义的复制操作【中间商赚差价】
直接缓冲区会绕开这种映射关系,直接去向操作系统申请一块由操作系统自己管理的内存区域,将数据直接读写到这个区域,操作系统也可以直接从上面读写。
一个很形象的比喻:
JVM和操作系统是两家合作的厂商。他们都有直接的运货车。若JVM向OS写数据【出货】
IO就是这样的步骤:
JVM的货车上货,中途OS的货车来了,验货之后将货物再拉到自己的车上,再运回去。
中间多了一次卸货、拉货的操作。
NIO是这样的操作:
直接叫对方的货车来拉货,这样就不用多余的卸装了。
但是有被对方黑了的危险。
NIO也不一定就是优越的,他主要有这些缺陷:
- 分配、销毁缓冲区困难,需要经过GC
- 不安全
- 缓冲区销毁、垃圾回收完全交由操作系统负责
三、 IO发展
IO发展历史:
(1)最原始IO:
CPU提供IO接口,CPU会轮询地检查个接口有没有IO请求,当发生IO请求就去处理。
由于CPU一直在轮训,没有时间做正经工作,因此CPU利用很低。
(2)DMA
系统新增一个DMA(Direct Memory Access)控制器,用来监控IO请求,每次察觉发生IO请求时,就向CPU提出DMA总线请求,由DMA控制器来进行IO
缺点:IO数量巨大时,发生大量的DMA总线申请,依然大量耗费CPU资源
(三)通道
提供新的硬件:通道处理器,完全独立的IO处理单元,不需要CPU的参与,可以进一步加大CPU利用率
获取通道:
使用代码示例:
和IO还是差不多的:
分散与聚集,编解码:
四、阻塞与非阻塞
在进行数据传输时,接收进程(通常为服务器进程)会一直循环等待发送进程的数据,只有数据验证通过真实有效,才会开始工作,之前的线程一直被阻塞
原来的解决办法:开辟多线程,其他线程
但是仍然解决不了这个线程的无故消耗
NIO:
提出了一个中间件,Selector【一个软件而不是硬件,是由NIO包提供的】,可以监视客户端的各条发送通道,当有通道有传输请求是,Selector会等待完成数据验证准备就绪之后,才会通知Server来进行IO请求。
在真正执行IO处理之前,Server可以一直做自己的事情而不被阻塞。