NIO
NIO简介
一. 概述
-
Java中IO分类:
-
BIO - Blocking IO - 同步阻塞式IO
-
NIO - New IO - NonBlocking IO - 同步非阻塞式IO (JDK1.4之后)
NIO在使用的时候可以基于事件驱动方式来实现
-
AIO - Asynchronous IO - 异步阻塞式IO(JDK1.7之后)
AIO基于NIO进行了改进,也把AIO称之为NIO.2;因为NIO出现时间比较早,所以市面上很多框架的底层都是基于NIO构建,对NIO进行了改进,导致AIO在市面上的占有率并不高。
-
-
两组概念:
- 同步和异步:如果一个对象或者一段逻辑在一个时间段内允许被多个线程同时使用,此时称之为异步;如果一个对象或者一段逻辑在一个时间段内只允许被一个线程使用,此时称之为同步.
- 阻塞和非阻塞:一个线程在没有获取到结果之前会持续等待也不执行逻辑也不抛出异常,这种现象称之为阻塞;一个线程即使没有获取到结果也会继续往下执行或者抛出异常,此时称之为非阻塞.
-
传统IO(BIO)的缺陷:
- 阻塞:导致任务的执行效率变低
- 一对一连接:每当客户端产生一个连接,服务器都需要产生一个线程去处理该连接;如果产生了大量的客户端连接,服务器就需要产生大量线程去处理该连接;而服务器所能产生和承载的线程数量是有限的,如果线程过多就会导致服务器卡顿甚至崩溃。
- 恶意连接:可能客户端连接之后不做任何操作,而是恶意保持连接,这样会导致服务器的线程无法释放。
- 单向传输:数据只能从一端传向另一端,如果需要反向传输需要令创建流对象
-
NIO的缺点:
- 在请求量比较大的情况下会出现部分请求的响应时间比较长的现象。
- 不适用于长任务场景,不然会导致其他的请求无法处理。
-
BIO和NIO的比较
BIO NIO 同步阻塞 同步非阻塞 单向传输数据 可以双向传输数据 一对一的连接方式 一对多的连接方式 面向流操作 面向缓冲区操作 适合于请求少、长连接场景 适合于大量请求、短连接的场景 -
NIO的三大组件:Buffer、Channel、Selector
二. Buffer - 缓冲区
- 作用:存储数据
- 基于数组实现,在内存中占用了一块连续的空间。Buffer针对基本类型来进行存储:ByteBuffer,ShortBuffer,IntBuffer,LongBuffer,FloatBuffer,DoubleBuffer,CharBuffer 。其中最基本常用的字节缓冲区ByteBuffer。
Ⅰ. ByteBuffer
ByteBuffer:字节缓冲区,继承了Buffer类,底层是依靠字节数组来存储数据。本身是一个抽象类,需要利用其子类创建对象或者是利用其提供的allocate或者是wrap方法来创建ByteBuffer对象
-
重要位置:capacity >= limit >= position>= mark
- capacity:容量位。用于表示缓冲区的容量,指定之后大小不能变 。
- limit:限制位。用于限制position所能达到的最大下标。当limit和position重合的时候,就表示所有元素已经遍历完毕。当缓冲区刚创建的时候,limit默认和capacity重合 。
- position:操作位。用于指向要读写的位置的。在缓冲区刚创建的时候,position默认为0。当对缓冲区进行读写操作的时候,position自动后挪。
- mark:标记位。用于进行标记。在缓冲区刚创建的时候指向-1,默认不启用
-
重要方法
方法 作用 allocate(int capacity) 创建缓冲区的时候指定缓冲区容量的大小,实际上是指定缓冲区底层的字节数组的大小 wrap(byte[] array) 利用传入的字节数组来构建缓冲区 array() 获取缓冲区底层的字节数组 get() 获取缓冲区中position位置上的字节 get(byte[] dst) 将缓冲区中的数据写到传入的字节数组中 get(int index) 获取指定下标上的字节 put(byte b) 向position位置上放入指定的字节 put(byte[] src) 向position位置上放入指定的字节数组 put(byte[] src, int offset, int length) 向position位置上放入指定的字节数组的部分元素 put(ByteBuffer src) 将字节缓冲区放入 put(int index, byte b) 向指定位置插入指定的字节 capacity() 获取容量位 clear() 清空缓冲区:position = 0; limit = capacity; mark = -1; flip() 反转缓冲区:limit = position; position = 0; mark = -1; hasRemaing() 判断position和limit之间是否还有空余 limit() 获取限制位 limit(int newLimit) 设置限制位 mark() 设置标记位 position() 获取操作位 position(int newPosition) 设置操作位 remaining() 获取position和limit之间剩余的元素个数 reset() 重置缓冲区:position = mark rewind() 重绕缓冲区:position = 0; mark = -1
三. Channel - 通道
- 作用:传输数据
- Channel在传输的时候是针对缓冲区进行操作
- 常用的Channel
- 文件:FileChannel
- UDP:DatagramChannel
- TCP:SocketChannel,ServerSocketChannel
- Channel默认是阻塞的,手动设置为非阻塞
- Channel可以实现双向传输
- FileChannel:
- 面向文件的通道
- 可以利用FileChannel完成对文件的读写操作
- 利用FileChannel读取文件的时候,是先将文件中的内容映射到虚拟内存中,然后再读取到程序的缓冲区中
- FileChannel不能直接创建,可以利用FileInputStream、FileOutputStream、RandomAccessFile对象中的个体Channel()方法获取
- 如果是通过FileInputStream获取FileChannel,那么只能进行读取操作
- 如果是通过FileOutputStream获取FileChannel,那么只能进行写入操作
- 如果是通过RandomAccessFile获取FileChannel,那么可以进行读写操作
四. Selector - 多路复用选择器
- 作用:针对通道的指定事件来进行选择
- Selector在使用的时候针对非阻塞通道进行操作
- 针对了四类事件:connect、accept、read、write,四类事件定义在SelectionKey中
- 可以实现利用一个或者少量线程处理大量请求
- 适用于大量的段任务场景,不适用于长任务场景