Java NIO
1、简介
Java NIO( New IO/Non-blocking IO) 是从Java 1.4版本开始引入的一个新的IO API,可以替代标准的Java IO API。NIO与原来的IO有同样的作用和目的,但是使用的方式完全不同, NIO支持面向缓冲区的、基于通道的IO操作。 NIO将以更加高效的方式进行文件的读写操作。
- 同步/异步
客户端在请求数据的过程中,能否做其他事情?能:异步。不能:同步。 - 阻塞/非阻塞
客户端与服务端是否从头到尾始终有一个持续连接,以至于占用了通道,不让其它客户端连接。(这里客户端、服务端只是用于举例) - BIO(Blocking IO)
同步阻塞IO,即传统的IO方式。这种方式是面向流的。数据的读写必须阻塞在一个线程内等待其完成。这种方式在对于少量的读写请求时,可以使用多线程解决,即适用于低负载,低并发的应用程序。但面对大量的连接请求时,这种方式无法处理。因此,需要引入NIO。 - NIO(New IO/Non-blocking IO)
同步非阻塞IO。这种方式是面向缓冲区的。对于BIO提供的Socket和ServerSocket,NIO提供了SocketChannel和ServerSocketChannel。适用于高负载、高并发的应用程序。 - AIO(Asynchronous IO)
异步非阻塞IO。异步IO是NIO的改进版,基于事件和回调机制实现,即应用在操作后会返回,不会堵在那里,当后台处理完成后,操作系统再通知相应线程进行后续操作。这种方式会导致在执行别的方法时突然接到通知,需要保存当前的执行进度,然后再跳转到之前的操作,导致方法间来回切换。目前应用不广泛。
2、缓冲区
- 缓冲区
用于存储数据,在通道中传送。类似于火车,在发送发装数据,在接收处收数据。
在java中表示 一个用于特定基本数据类型的容器。由 java.nio 包定义的,所有缓冲区都是 Buffer 抽象类的子类 。 数据是从通道读入缓冲区,从缓冲区写入通道中的。 - Buffer常用子类
ByteBuffer、CharBuffer、ShortBuffer、IntBuffer、LongBuffer、FloatBuffer、DoubleBuffer - 获取Buffer对象
static XxxBuffer allocate(int capacity) : 创建一个容量为 capacity 的 XxxBuffer 对象 - Buffer相关概念
- 容量 (capacity) : 表示 Buffer 最大数据容量,缓冲区容量不能为负,创建后不能更改。
- 限制 (limit): 第一个不应该读取或写入的数据的索引,即位于 limit 后的数据不可读写。缓冲区的限制不能为负,并且不能大于其容量。
- 位置 (position): 下一个要读取或写入的数据的索引。缓冲区的位置不能为负,并且不能大于其限制
- 标记 (mark)与重置 (reset): 标记是一个索引,通过 Buffer 中的 mark() 方法指定 Buffer 中一个特定的 position,之后可以通过调用 reset() 方法恢复到这个 position.
- 标记、 位置、 限制、 容量遵守以下不变式: 0 <= mark <= position <= limit <= capacity
- 数据操作
- 读
get() :读取单个字节
get(byte[] dst):批量读取多个字节到 dst 中
get(int index):读取指定索引位置的字节(不会移动 position) - 写
put(byte b):将给定单个字节写入缓冲区的当前位置
put(byte[] src):将 src 中的字节写入缓冲区的当前位置
put(int index, byte b):将指定字节写入缓冲区的索引位置(不会移动 position)
- 直接与非直接缓冲区
应用程序不能直接从物理磁盘中读取数据,先要将数据读到内核地址空间中,再复制到用户地址空间,再读到应用程序,写数据同理。
在上述过程中,复制操作可以省略,因此引入直接缓冲区即物理内存映射文件,不用再进行复制操作。
非直接缓冲区:通过 allocate() 方法分配缓冲区,将缓冲区建立在 JVM 的内存中。
直接缓冲区:通过 allocateDirect() 方法分配直接缓冲区,将缓冲区建立在物理内存中。可以提高效率。
3、通道
打开到IO设备(套接字、文件等)的连接,负责在其中传输数据。类似于铁轨,用于连接两方的线路。
从计算机原理上理解,传统的IO接口需要CPU负责,当有大量的IO接口中需要传输数据时,占用大量的CPU资源。后来引入DMA,直接访问内存,不需要CPU过多干预,这种方式在有大量的数据请求时,会需要大量的DMA总线,这时会造成总线冲突。因此后来又引入了通道,通道是一个独立的处理器,拥有一套自己的命令,完全独立出来用于IO操作。
- Channel常见实现类
- FileChannel:用于读取、写入、映射和操作文件的通道。
- DatagramChannel:通过 UDP 读写网络中的数据通道。
- SocketChannel:通过 TCP 读写网络中的数据。
- ServerSocketChannel:可以监听新进来的 TCP 连接,对每一个新进来的连接都会创建一个 SocketChannel。
- 获取通道
获取通道的一种方式是对支持通道的对象调用getChannel() 方法。支持通道的类如下:
本地IO:
- FileInputStream/FileOutputStream
- RandomAccessFile
网络IO:
- DatagramSocket
- Socket
- ServerSocket
jdk1.7之后:
- 获取通道的其他方式是使用 Files 类的静态方法 newByteChannel() 获取字节通道。
- 通过通道的静态方法 open() 打开并返回指定通道。
- transferFrom() 函数: 将数据从源通道传输到其他 Channel 中。
- transferTo() 函数: 将数据从源通道传输到其他 Channel 中。
4、分散与聚集
- 分散读取(Scattering Reads):将通道中的数据分散到多个缓冲区中
- 聚集写入(Gathering Writes):将多个缓冲区中的数据聚集到通道中
5、字符集
- 编码:字符串 -> 字节数组
- 解码:字节数组 -> 字符串
6、选择器(Selector)
- 选择器( Selector) 是 SelectableChannle 对象的多路复用器, Selector 可以同时监控多个 SelectableChannel 的 IO 状况,也就是说,利用 Selector可使一个单独的线程管理多个 Channel。 Selector 是非阻塞 IO 的核心。
- SelectionKey: 表示 SelectableChannel 和 Selector 之间的注册关系。每次向选择器注册通道时就会选择一个事件(选择键)。 选择键包含两个表示为整数值的操作集。操作集的每一位都表示该键的通道所支持的一类可选择操作 。
- 当调用 register(Selector sel, int ops) 将通道注册选择器时,选择器对通道的监听事件,需要通过第二个参数 ops 指定。
- 可以监听的事件类型( 可使用 SelectionKey 的四个常量表示):
读 : SelectionKey.OP_READ ( 1)
写 : SelectionKey.OP_WRITE ( 4)
连接 : SelectionKey.OP_CONNECT ( 8)
接收 : SelectionKey.OP_ACCEPT ( 16) - 若注册时不止监听一个事件,则可以使用“位或”操作符连接
7、SocketChannel
- Java NIO中的SocketChannel是一个连接到TCP网络套接字的通道。
- 操作步骤:
打开 SocketChannel
读写数据
关闭 SocketChannel - Java NIO中的 ServerSocketChannel 是一个可以监听新进来的TCP连接的通道,就像标准IO中的ServerSocket一样。
8、DatagramChannel
- Java NIO中的DatagramChannel是一个能收发UDP包的通道。
- 操作步骤:
打开 DatagramChannel
接收/发送数据
9、管道(Pipe)
- Java NIO 管道是2个线程之间的单向数据连接。Pipe有一个source通道和一个sink通道。数据会被写到sink通道,从source通道读取。
10、NIO2
- 概念
JDK 7 对NIO进行了极大的扩展,增强了对文件处理和文件系统特性的支持,称为 NIO.2。因为 NIO 提供的一些功能, NIO已经成为文件处理中越来越重要的部分。
- Path 与 Paths
- java.nio.file.Path 接口代表一个平台无关的平台路径,描述了目录结构中文件的位置。
- Paths 提供的 get() 方法用来获取 Path 对象:
Path get(String first, String … more) : 用于将多个字符串串连成路径。
- Files 类
- java.nio.file.Files 用于操作文件或目录的工具类。
11、自动资源管理
- 概念
Java 7 增加了一个新特性,该特性提供了另外一种管理资源的方式,这种方式能自动关闭文件。这个特性有时被称为自动资源管理(Automatic Resource Management, ARM), 该特性以 try 语句的扩展版为基础。自动资源管理主要用于,当不再需要文件(或其他资源)时,可以防止无意中忘记释放它们。 - 自动资源管理基于 try 语句的扩展形式:
try(需要关闭的资源声明){
//可能发生异常的语句
}catch(异常类型 变量名){
//异常的处理语句
}
……
finally{
//一定执行的语句
} 当
try 代码块结束时,自动释放资源。因此不需要显示的调用 close() 方法。该形式也称为“ 带资源的 try 语句” 。
注意:
①try 语句中声明的资源被隐式声明为 final ,资源的作用局限于带资源的 try 语句
②可以在一条 try 语句中管理多个资源,每个资源以“;” 隔开即可。
③需要关闭的资源,必须实现了 AutoCloseable 接口或其自接口 Closeable