NIO-简单入门
前段时间在学习项目中加入了Dubbo框架,说实话,也就用一下,至于原理,是真的不懂多少。这不,当我想要了解Dubbo的原理时,出现了个陌生词汇Netty,这下好了,一个没搞懂,又来一个,那就学Netty呗,然鹅,是这么简单吗,Netty的底层是NIO,what?啥是NIO鸭,那从这篇开始,我就扒一扒NIO那些事,其实NIO还能往深了扒,什么计组、操作系统等,奈何我层次不够,暂且不扒了,等我学习下
先来说下传统IO(BIO)
传统的IO指的是平常用到的那些输入流/输出流、字节流/字符流等,IO是面向流的,单向的,IO的各种流都是阻塞的,即在进行read()和write()操作时,线程直接阻塞,该线程将不能做任何事情;当来了新的请求,就只能重新开一个线程来处理这个请求,但也同样会阻塞
再说下NIO
NIO是在JDK1.4中新出现的内容,其作用和IO是一致的,但是实现方法和作用是不同的,它是面向缓冲区、双向操作的、非阻塞的IO
IO和NIO的区别
IO | NIO |
面向流 | 面向缓冲区 |
阻塞IO | 非阻塞IO |
无 | Selector选择器 |
面向流与面向缓冲
IO是面向流进行操作的,即每次从流中读一个或多个字节,直到读取所有的字节,这些数据
没有被缓存起来,需要一次性读取或写入,且在此过程中线程是阻塞状态;六种的数据不能
移动,如果需要移动,需要将六种的数据放到缓冲区中
NIO是面向缓冲区的,即数据是被读取到缓冲区中,可以基于缓冲区对其中的数据进行移动等
操作,但是需要判断该缓冲区中是否包含所需的数据,且需要保证当缓冲区内数据未处理完成时,不能
被新的数据覆盖掉
阻塞IO与非阻塞IO
IO的各种流都是阻塞的,当一个线程调用流的read()或write()时,该线程会被阻塞,
直到有一些数据被读取,或数据完全写入,期间不能干其他事情,CPU转去处理其他线
程,假设一个线程监听一个端口,一天只会有几次请求进来,但是CPU不得不为该线程
不断的做上下文切换,而且大部分切换都是以阻塞告终,这是极其浪费系统资源的
非阻塞IO,是一个线程在读操作时,如果没有数据可读,就什么都不会获取,而不是
阻塞;写操作时,再将数据写入某个通道时,不需要等它完全写入,这个线程就可以
去做其他事情了
NIO通信是利用事件驱动机制,而不是监听机制,事件到了再触发,NIO线程之间通过
wait、notify等方式通知,保证每次上下文切换都有意义,避免系统资源的浪费
选择器
Java NIO的选择器就是将每个请求通道都注册进来,然后对这些通道进行监视
,由专门的线程来选择通道进行执行,这种选择机制,可以使一个单独的线程
很容易的来管理多个通道
NIO的主要元素
通道(channel):标识打开到IO设备(如:文件、套接字)的链接,跟IO中的流差不多,负责传输
通道的主要实现类
FileChannel
SocketChannel
ServerSocketChannel
DatagramChannel
通道的获取
(1)Java针对支持通道的类提供了getChannel()方法
本地IO:
FileInputStream/FileOutputStream
RandomAccessFile
网络IO:
Socket
ServerSocket
DatagramSocket
(2)在JDK1.7中的NIO.2 针对各个通道提供了静态方法 open()
(3)在JDK1.7中的NIO.2 的Files工具类的newByteChannel()
缓冲区(Buffer):Java NIO中的Buffer用于和NIO通道进行交互。数据是从通道读入缓冲区,从缓冲区写入到通道中的
作用:数据的操作主要是在缓冲区中处理,负责存储
结构:底层是数组,用来存储不同数据类型的数据
分类:ByteBuffer、CharBuffer、ShortBuffer、IntBuffer、LongBuffer、FloatBuffer、DoubleBuffer
API:
获取缓冲区 :
allocateDirect():分配直接缓冲区
存入数据到缓冲区中 :
获取缓冲区中的数据 :
切换读模式 :
reset():恢复到mark位置
查看缓冲区中是否还有可操作的数据 :
获取缓冲区中可操作的数据 :
清除已经读过的数据 :
可重复读,将position置为0 :
四个核心属性:
容量,表示缓冲区中最大存储数据的容量,一旦声明不能改变 :
limit:界限,表示缓冲区中可以操作数据的大小(limit后面的数据不能读写)
position:位置,表示缓冲区中正在操作数据的位置
mark:位置,标记的位置
0 < mark < position <= limit <= capacity
直接缓冲区和非直接缓冲区
非直接缓冲区:通过allocate()方法分配缓冲区,将缓冲区建立在JVM的内存中
直接缓冲区:通过allocateDirect()方法分配直接缓冲区,将缓冲区建立在物理内存中,可以提高效率
获取直接缓冲区的方式:
ByteBuffer.allocateDirect
通道内存映射文件:
FileChannel inChannel = FileChannel.open(...)
MappedByteBuffer inMapperBuf = inChannel.map(...)
使用Buffer读写数据一般遵循以下四个步骤:
写入数据到Buffer
调用flip()方法
从Buffer中读取数据
调用clear()方法或者compact()方法
选择器(Selector):管理所有的通道,根据选择键来对应处理通道
Selector可以监听的事件类型
读:SelectionKey.OP_READ
写:SelectionKey.OP_WRITE
连接:SelectionKey.OP_CONNECT
接收:SelectionKey.OP_ACCEPT
监听多个事件可以用 “|” 位或操作符进行连接
今天就先说到这里,其实很多概念看下代码更容易理解,后面的话会对内容再进行补充,包括学习下netty,dubbo等原理
由于篇幅问题,文中不展示大篇幅的代码,示例代码均已上传到码云,如需更详细的了解,请自行下载代码进行测试
https://gitee.com/MaYunJerryLee/urmd-code-demo/tree/master/urmd-nio-demo