我个人理解的IO就是计算机和磁盘、网卡等设备进行交互、信息传输的过程。
从计算机结构的视角来看的话, I/O 描述了计算机系统与外部设备之间通信的过程。
从应用程序方面来看,I/O涉及了用户态和内核态
像我们平常运行的应用程序都是运行在用户空间,只有内核空间才能进行系统态级别的资源有关的操作,比如文件管理、进程通信、内存管理等等。也就是说,我们想要进行 IO 操作,一定是要依赖内核空间的能力。
用户空间的程序不能直接访问内核空间。
因此,用户进程想要执行 IO 操作的话,必须通过 系统调用 来间接访问内核空间
这里就出现了以下的拷贝

当用户发起I/O请求,会发生如下动作:
可以看到,用户态与内核态的切换发生了 3 次,数据交换了四次。
在广义上,IO模型分为五类,分别是:阻塞IO,非阻塞IO,多路复用IO,信号驱动IO,异步IO
以Java的NIO为例,当调用一次 channel.read 或 stream.read 后,会切换至操作系统内核态来完成真正数据读取,而读取又分为两个阶段,分别为:

对于阻塞而言,如果没有read到数据,就一直等到这里,这就是阻塞的
如果没有read到数据,但是程序可以返回,就是非阻塞的
阻塞调用是指调用结果返回之前,当前线程会被挂起,一直处于等待消息通知,不能够执行其他业务。函数只有在得到结果之后才会返回
而对于同步来说,发出read后,当有数据了,我再去复制数据
但是异步来说,我发出了read,有其他的线程得到数据,并别将数据返回给发出read的线程,发出read的线程只需要接收就可以了,不用管等待和发出数据,这就是异步
异步的概念和同步相对。当一个同步调用发出后,调用者要一直等待返回消息(结果)通知后,才能进行后续的执行;当一个异步过程调用发出后,调用者不能立刻得到返回消息(结果)。实际处理这个调用的部件在完成后,通过状态、通知和回调来通知调用者
阻塞IO

从发出read请求后,直到数据读到用户线程,在这个过程中,用户线程一直处于阻塞状态
非阻塞IO

多路复用

Java用selector监听事件,当有有事件发生时,select从阻塞状态返回,接着处理发生的多个事件
异步 IO

发出read请求,用户线程继续执行,当复制数据的步骤完成,线程2将数据带回给用户线程。
阻塞 IO vs 多路复用


零拷贝指的是数据无需拷贝到 JVM 内存中,同时具有以下三个优点
传统的 IO 将一个文件通过 socket 写出
File f = new File("data.txt");
RandomAccessFile file = new RandomAccessFile(file, "r");
byte[] buf = new byte[(int)f.length()];
file.read(buf);
Socket socket = ...;
socket.getOutputStream().write(buf);
内部工作流程是这样的:

就如 上面说的一样:
内核态和用户态直接出现了三次切换,被数据拷贝了四次
通过 DirectByteBuf

进一步优化(底层采用了 linux 2.1 后提供的 sendFile 方法),java 中对应着两个 channel 调用 transferTo/transferFrom 方法拷贝数据

可以看到

整个过程仅只发生了一次用户态与内核态的切换,数据拷贝了 2 次。所谓的【零拷贝】,并不是真正无拷贝,而是在不会拷贝重复数据到 jvm 内存中,零拷贝的优点有