1 简介
Java NIO是从java 1.4版本开始引入的一个新的IO API
- 目的—替代标准版的Java IO API
NIO与原来的IO有同样的作用和目的,但是使用方式完全不同
- NIO支持面向缓冲区的,基于通道的操作
- NIO以更高效的方式进行文件的读写操作
2 与IO的主要区别
IO
IO | NIO |
---|---|
面向流(Stream Oriented) | 面向缓冲区(Buffer Oriented) |
阻塞IO(Blocking IO) | 非阻塞IO(Non Blocking IO) |
无 | 选择器(Selectors) |
3 通道和缓冲区
NIO系统的核心在于:
- 通道(Channel)—打开到IO设置(例如:文件, 套接字)连接
- 缓冲区(Buffer)—一个用于特定基本数据类型的容器
由java.nio包定义,所有缓冲区都是Buffer抽象类的子类
若需要使用NIO系统
- 需要获取用于连接IO设备的通道以及用于容纳数据的缓冲区.
- 然后操作缓冲区,对数据进行处理
简而言之, Channel负责传输,Buffer负责存储
3.1 缓冲区
缓冲区(Buffer):在Java NIO中负责数据的存取. 缓冲区就是数组,用于存储不同数据类型的数据
根据数据类型不同(Boolean除外),提供了相应的缓冲区
- ByteBuffer
- CharBuffer
- ShortBuffer
- IntBuffer
- LongBuffer
- FloatBuffer
- DoubleBuffer
上述缓冲区的管理方式几乎一致,通过allocate
获取缓冲区
缓冲区存取数据的两个核心方法
put()
—存入数据到缓冲区中get()
—获取缓冲区中的数据
缓冲区中的四个核心属性
capacity
—容量,标识缓冲区最大存储数据的容量,一旦声明就不能改变limit
—界限,表示缓冲区中可以操作数据的大小limit后面的数据不能读写
position
—位置,表示缓冲区中正在操作数据的位置
position <= limit <= capacity
mark
—标记, 表示记录当前position的位置,可以通过reset()恢复到mark位置
0 <= mark <= position <= capacity
3.2 直接缓冲区与非直接缓冲区
- 非直接缓冲区—通过
allocate()
方法分配缓冲区,将缓冲区建立在JVM的内存中 - 直接缓冲区—通过
allocateDirect()
方法分配直接缓冲区,将缓冲区建立在物理内存中,可以提高效率
字节缓冲区要么是直接的,要么是非直接的. 如果为直接字节缓冲区,则JVM会尽最大努力直接在此缓冲区上执行本机的I/O操作
也就是说,在每次调用基础操作系统的一个本机I/O操作之前(或之后),虚拟机都会尽量避免将缓冲区中的内容复制到中间缓冲区中(或从中间缓冲区中复制内容)
直接字节缓冲区可以通过调用此类的allocateDirect()
方法来创建. 此方法返回的缓冲区进行分配和取消分配所需成本通常高于非直接缓冲区. 直接缓冲区的内容可以在主流在常规的垃圾回收器之外. 因此, 他们对应用程序的内容需求量造成的影响可能并不明显. 所以, 建议将直接缓冲区主要分配给哪些容易受基础系统的本机I/O操作影响的大型, 持久的缓冲区.
一般情况下,最好仅在直接缓冲区能在程序性能方面带来明显好处时分配它们
直接缓冲区还可以通过FileChannel的map()
方法将文件区域直接映射到内存中来创建. 该方法返回MappedByteBuffer
.
Java平台的实现有助于通过JNI从本机代码创建直接字节缓冲区.
如果以上这些缓冲区中某个缓冲区实例指的是不可用访问的内存区域,则试图访问该区域不会更改该缓冲区的内容.并且将会在访问期间或稍后的某个时间导致抛出不确定的异常
字节缓冲区是直接缓冲区还是非直接缓冲区可以通过其isDirect()
方法来确定.提供此方法是为了能够在性能关键代码中执行显示缓冲区管理
如下图所示
3.3 通道
通道(Channel)—由java.nio.channels
包定义的
Channel表示IO源与目标打开的连接
用于源节点和目标节点的连接, 在Java NIO中负责缓冲区中的数据传输. Channel本身不存储数据,因此需要配合缓冲区进行传输
Channel类似于传统的"流", 只不过Channel本身不能直接访问数据,Channel只能与Buffer进行交互
CPU直接处理I/O请求
通过DMA—直接存储器处理IO请求
通过通道处理IO请求
3.3.1 主要实现类
java.nio.channels.Channel
接口
FileChannel
—文件传输SocketChannel
—网络传输(客户端)ServerSocketChannel
—网络传输(服务端)
-DatagramChannel
—网络传输, UDP
获取通道
- Java针对支持通道的类提供了getChannel()方法
- 本地IO
FileInputStream/FileOutputStream
RandomAccessFile
- 网络IO
- Socket
ServerSocket
DatagramSocket
- 在JDK 1.7中的NIO针对各个通道提供了静态方法
open()
- 在JDK 1.7中的
Files
工具类的newByteChannel()
- 通道之间的数据传输
transferFrom
transferTo
两者也是通过直接缓冲区传递数据
3.4 分散(Scatter)和聚集(Gather)
- 分散读取
Scatter Reads
是指从Channel中读取的数据分散到各个Buffer中
注意: 按照缓冲区的顺序,从Channel中读取的数据依次将Buffer填满
- 聚集写入
Gather Writes
是指将多个Buffer中的数据"聚集"到Channel
注意: 按照缓冲区的顺序,写入position和limit之间的数据到Channel
3.5.字符集
Charset
- 编码—字符串 —> 字节数组
- 解码—字节数组 —>字符串
4 NIO非阻塞式
4.1 阻塞与非阻塞
传统的的IO流都是阻塞的. 也就是说,当一个线程调用read()或write()时,该线程被阻塞, 直到有一些数据被读取或写入, 该线程在此期间不能执行其他任务.
因此, 在完成网络通信进行IO操作时, 由于线程会阻塞, 所以服务器补习为每个客户端都提供一个独立的线程进行处理, 当服务端需要处理大量客户端时, 性能急剧下降
Java NIO是非阻塞式的. 当线程从某通道进行读写数据时, 若没有数据可用时, 该线程可以进行其他任务, 线程通常将非阻塞IO的空闲时间用于在其他通道上执行IO操作, 所以单独的线程可以管理多个输入和输出通道
因此, NIO可以让服务端使用一个或有限几个线程来同时处理连接到服务器端的所有客户端
4.2 NIO
使用NIO完成网络通信的三个核心
- 通道(Channel)—负责连接
- 缓冲区(Buffer)—负责数据的存储
- 选择器(Selector)—是SelectableChannel的多路复用器
用于监控SelectableChannel的IO状况
4.3 选择器
选择器(Selector)是SelectableChannel对象的多路复用器
Selector可以同时监控多个SelectableChannel的状态
, 也就是说, 利用Selector可以使一个单独的线程管理多个Channel. Selector是非阻塞IO的核心
SelectableChannel的结构如下:
- SelectableChannel
- AbstractSelectableChannel
- SocketChannel
- ServerSocketChannel
- DatagramChannel
- AbstractSelectableChannel
4.3.1 SelectionKey
-
当调用register(Selector sel, int pos) 将通道注册选择器时, 选择器对通道的监听事件,需要通过第二个参数ops指定
-
可以监听的事件类型(可使用SelectionKey的四个常量表示)
- 读—SelectionKey.OP_READ
- 写—SelectionKey.OP_WRITE
- 连接—SelectionKey.OP_CONNECT
- 接收—SelectionKey.OP_ACCEPT
若注册时不止监听一个事件,则可以使用"位或"操作符连接