文章目录
前言
- 鉴于现在业界大多数情况下不怎么问
AIO
(对于我而言), 这篇面试总结仅仅只是关于BIO
与NIO
阻塞与非阻塞
- 在进程访问数据的时候, 数据是否准备就绪的一种处理方式, 当数据没有准备就绪时:
Block IO:
是一种传统的一种I/O
, 也就是说在read/write
的过程中会发生阻塞的现象, 往往是需要等待缓冲区中的数据贝准好之后才处理其他的事情, 否则会一直阻塞. 当用户线程发出I/O
请求之后, 内核会查看缓冲区中的数据是否准备好, 如果没有准备好, 用户线程就会等待数据准备好并且进入阻塞状态. 用户线程就放弃CPU
控制权. 在数据准备好之后, 内核就会将缓冲区中的数据拷贝到用户线程, 并返回结果给用户线程, 用户线程才会解除阻塞状态.None-Block I/O:
当用户线程访问数据缓冲区时, 无论数据是否准备好都直接得到结果, 不会进行等待, 如果是一个Error
情况下, 就直到数据并没有准备好, 将再次访问数据缓缓冲区, 一旦内核中的数据准备就绪, 并且再次接收到用户线程的请求时, 就会把数据拷贝到用户线程并返回, 在None-Block I/O
中, 用户线程需要不断地询问内核数据是否就绪, 会一直占用着CPU
资源
同步与异步
- 同步与异步都是基于
应用程序
和OS
处理I/O
事件采用的方式,
同步 :
是应用程序需要参与I/O
的读写操作, 并且在处理I/O
事件的时候, 必须阻塞在某个方法上面等待内核中的数据准备就绪, 在这个情况下, 无论是read/write
操作, 我们就不能进行下面的操作异步 :
所有的I/O
的读写操作交给OS
处理, 应用程序只是需要等待通知, 在这个情况下, 我们可以去做其他事情, 并不需要去阻塞等待I/O
完成操作, 当I/O
完成操作之后会给我们一个通知.【例: Ajax】
, 从内核的角度, 当收到了一个异步读之后就会马上返回, 说明读请求已经发送成功, 所以不会对用户线程产生阻塞. 接着, 内核会等待数据准备就绪, 然后将数据拷贝到用户线程, 也就是说用户线程完全不需要知道的整个IO
操作是怎么进行, 只是先发起一个请求, 在接收到内核返回的成功通知的时候, 就表示I/O
操作已经完成.
JAVA BIO 包
JAVA NIO
NIO 网络模型图
NIO 核心对象之 Channel
Channel
可以用来进行读与写的操作,read/write
操作的数据都是通过Buffer
对象进行处理, 对读操作的时候会把数据读入缓冲区, 而写操作的时候会从缓冲区获取该数据
Channel 主要的实现类
扫描二维码关注公众号,回复:
9746139 查看本文章
NIO 核心对象之 Buffer
Buffer
是一个容器, 是一个连续数组, 在NIO
中, 所有的读/写数据操作都是通过Buffer
Buffer 读写操作图
Buffer 的主要实现类
Buffer 实现原理
Buffer
是一个特殊的数组, 其内置了一些机制, 能够跟踪和记录缓冲区中的状态变化情况, 如果使用Buffer#read()/flip()/get()/clear()
方法时, 都会引起缓冲区状态的变化
// Invariants: mark <= position <= limit <= capacity
private int mark = -1;
private int position = 0;
private int limit;
private int capacity;
Buffer 状态值测试代码
package com.cj.nio;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
/**
* @Author: CaoJun
* @Create: 2020-03-11 06:03
**/
public class BufferTest {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream(new File("C:\\Users\\john\\Desktop\\nio\\bufferTest.txt"));
// 获取文件操作管道
FileChannel fileChannel = fis.getChannel();
// 分配大小为 10 的缓冲区
ByteBuffer byteBuffer = ByteBuffer.allocate(10);
printBufferStatus("初始化缓冲区大小 ", byteBuffer);
// 读取缓冲区
fileChannel.read(byteBuffer);
printBufferStatus("调用 read() 方法", byteBuffer);
// 锁定操作的范围
byteBuffer.flip();
printBufferStatus("调用 flip() 方法", byteBuffer);
// 判断文件中是否有数据
while (byteBuffer.remaining() > 0) {
byteBuffer.get();
}
printBufferStatus("调用 get() 方法", byteBuffer);
// 清除缓冲区中的数据
byteBuffer.clear();
printBufferStatus("调用 clear() 方法", byteBuffer);
// 关闭管道
fileChannel.close();
}
/**
* 打印缓冲区状态
*/
private static void printBufferStatus(String step, Buffer buffer) {
System.out.print(step + " : ");
//容量,数组大小
System.out.print("capacity: " + buffer.capacity() + ", ");
//当前操作数据所在的位置,也可以叫做游标
System.out.print("position: " + buffer.position() + ", ");
//锁定值,flip,数据操作范围索引只能在 position - limit 之间
System.out.println("limit: " + buffer.limit() + "\r\n");
}
}
Buffer 状态值测试结果
bufferTest.txt 文件中的数据
Buffer 状态值变动图
NIO 核心对象之 Selector
NIO
中的None-Block I/O
采用基于Reactor
模式的工作方式, 在I/O
调用时不会被阻塞.Selector
是NIO
的核心类,Selector
能够检测多个注册的通道上是否有事件发生, 如果有事件发生时, 就获取事件并对每个事件都进行相应的响应处理, 这样就可以只用一个线程就可以管理过个通道. 所以只有在连接读写事件的操作时, 才会调用方法进行读写操作, 这样就大大地减少了系统的开销, 并且不用为每一个连接创建线程, 不用维护多个线程并避免了多线程之间的上下文切换开销.- 在下图中无论读/写等任何注册的时间发生的时候, 可以从
Selector
中获得相应的SelectionKey
, 同时从SelectionKey
中可以找到发生的时间和该时间所发生的具体的SelectableChannel
去获取客户端发送过来的数据- 使用
NIO
中的非阻塞I/O
编写服务器处理程序分为以下几个步骤
- 向
Selector
对象注册感兴趣的事件- 从
Selector
对象中获取感兴趣的事件- 根据不同的事件进行相应的处理
总结 BIO 与 NIO 对比
面向流和面向缓冲 :
BIO
是面向流的, 意味着每次从六中读1-n
个字节, 知道读取出所有的字节, 读取出的字节并没有被缓存, 也不能前后移动流中的数据, 如果需要前后移动从流中读取的数据, 就需要先将它们缓存到一个缓冲区中,NIO
是面向缓冲的, 意味着每次读取到的数据先到缓冲区中, 当需要的时候可以在缓冲区中进行前后移动, 这样就增加了处理过程中的灵活性阻塞和非阻塞 :
BIO
中的流是阻塞的, 意味着用户线程每次进行读/写操作的时候都会被阻塞, 直到有一些数据被读取, 或者完成数据的写书, 与此同时该用户线程不能做其他操作, 如果没有可用数据的情况下, 就什么都不会获取, 而不是保证线程阻塞, 所以直到数据能在可以读取的状态下之前, 该线程还可以去做其他的操作.NIO
中的通道是非阻塞的, 一个用户线程请求写入数据到Channel
中, 但是不需要等待它完全写入, 该用户线程还可以去做其他的操作. 用户线程通常将None-Block I/O
的空闲时间用于在其他通道上执行I/O
操作, 所以一个单独的线程就可以管理n
个输入/输出通道
选择器 :
Java NIO
中的Selector
可以一个线程去监视n
个通道, 并且可以注册多个Channel
同时使用一个Selector
, 然后使用一个单独的线程来选择通道, 这些通道里已经有可以处理的输入, 或者选择已经准备写入的通道, 这种选择机制会使一个单独的线程很容易地管理n
个通道
IO模型 | BIO | NIO |
---|---|---|
通信 | 面向流 | 面向缓冲 |
处理 | 阻塞IO (Multi Thread) | 非阻塞IO (Reactor) |
触发 | 无 | 选择器 (Round) |
- Okay, BIO and NIO are finished right now. Nothing is impossible as long as you are willling to do it,