文章目录
NIO 简介
- 传统I/O库与NIO最重要的区别是数据的打包和传输的方式,传统的I/O以流的方式处理数据,一次一个字节地处理数据,一个输入流产生一个字节的数据,一个输出流消费一个字节的数据。NIO以块的方式处理数据,每一个操作都在一步中产生或消费一个数据块,按块处理数据比按字节流处理数据要快的多。BIO是面向流的,每次从流中读取一个或多个字节,直到读取完所有的字节,没有缓存在任何地方,流不能前后移动流中的数据,如需前后移动处理,需要先将其缓存至一个缓冲区。Java NIO面向缓冲,数据会被读取到一个缓冲区,需要时可以在缓冲区中前后移动处理,这增加了处理过程的灵活性。但与此同时在处理缓冲区前需要检查该缓冲区中是否包含有所需要处理的数据,并需要确保更多数据读入缓冲区时,不会覆盖缓冲区内尚未处理的数据。
- NIO采用内存映射文件的方式来处理输入输出,NIO将文件或文件的一段区域映射到内存中,这样就可以像访问内存一样访问文件了
I/O内存缓冲区
用户空间
:常规进程所在区域,JVM就是常规进程,该区域执行的代码不能直接访问硬件设备内核空间
:操作系统所在区域。内核代码它能与设备控制器通讯,控制着用户区域进程的运行状态等等。最重要的是,所有的I/O都直接或间接通过内核空间数据交互
:当用户(java)进程进行I/O操作的时候,它会执行一个系统调用将控制权移交给内核,内核代码负责找到请求的数据,并将数据传送到用户空间内的指定缓冲区内核空间缓冲区
:内核代码读写数据要通过磁盘的I/O操作,由于磁盘I/O操作的速度比直接访问内存慢了好几个数量级,所以内核代码会对数据进行高速缓存或预读取到内核空间的缓冲区(减少磁盘I/O,提高性能)用户空间缓冲区
:同上,java I/O进程通过系统调用读写数据的速度,要比直接访问虚拟机内存慢好几个数量级,所以可以执行一次系统调用时,预读取大量数据,缓存在虚拟机内存中。DMA(Direct Memory Access 直接内存访问)
:是一种内存访问技术,它允许某些计算机内部的硬件子系统(计算机外设),可以独立地直接读写系统内存,而不需CPU介入处理,在同等程度的处理器负担下,DMA
是一种快速的数据传送方式。磁盘控制器
:硬盘控制器即磁盘驱动器适配器,是计算机与磁盘驱动器的接口设备,它接收并解释计算机来的命令,向磁盘驱动器发出各种控制信号。- 关系图
- 表面上看,把数据从
内核空间
拷贝到用户空间
似乎有些多余。为什么不直接让磁盘控制器把数据送到用户空间的缓冲区呢?这样做有几个问题- 首先,硬件通常不能直接访问用户空间
- 其次,像磁盘这样基于块存储的硬件设备操作的是固定大小的数据块,而用户进程请求的可能是任意大小的或非对齐的数据块。在数据往来于用户空间与存储设备的过程中,内核负责数据的分解、再组合工作,因此充当着中间人的角色
IO模型
阻塞I/O
阻塞I/O(blocking I/O)
模型是最流行的I/O模型,默认情况下,所有套接字和文件描述符就是阻塞的。阻塞I/O将使请求进程阻塞,直到请求完成或出错BIO
的阶段- 等待数据就绪。例如套接字有数据到来,文件描述符可读。
- 将数据从内核空间拷贝到用户空间。
非阻塞I/O
- 非阻塞I/O(nonblocking I/O):如果I/O操作会导致请求进程睡眠,则不要把它挂起,而是返回一个错误告诉它。相比于BIO,NIO的第一个阶段不会阻塞,相反是一直在对非阻塞描述符调用read 或 recvfrom 等操作。当一个应用进程像这样对一个非阻塞描述符调用 recvfrom 时,我们称之为轮询
- 应用进程持续轮询内核,以查看某个操作是否就绪。这样做往往消费大量CPU时间,不过这种模型偶尔也会遇到。在嵌入式开发中,非阻塞I/O模型比较常见。比如在编写设备驱动的时候,常常会短暂地轮询设备状态寄存器,以等待设备就绪。
IO复用
- I/O 多路复用(I/O multiplexing)会用到 select 或者 poll 函数,这两个函数也会使进程阻塞,但是和阻塞 I/O 所不同的的,这两个函数可以同时阻塞多个 I/O 操作。而且可以同时对多个读操作,多个写操作的 I/O 函数进行检测,直到有数据可读或可写时,才真正调用 I/O 操作函数。Java NIO就是这种模型