一、普通io
基础:read/write
系统调用
效率:
下面的程序只使用read和write函数复制一个文件
#include "apue.h"
#define BUFFSIZE 4096
int main(void)
{
int n;
char buf[BUFFSIZE];
while ((n = read(STDIN_FILENO, buf, BUFFSIZE)) > 0)
if(write(STDOUT_FILENO, buf, n) != n)
err_sys("write error");
if(n < 0)
err_sys("read error");
exit(0);
}
上面的程序有一个疑问点是BUFFSIZE为什么选取4096,在回答此问题之前,让我们先用各种不同的BUFFSIZE值来运行此程序。下图显示了用20种不同的缓冲区长度,读516581760字节的文件所得到的效果。
此测试所用的文件系统是Linux ext64文件系统,其磁盘块长度为4096字节(磁盘块长度由st_blksize表示),CPU时间的几个最小值差不多出现在BUFFSIZE为4096及以后的位置,继续增加缓冲区长度对此时间几乎没有影响。
大多数文件系统为改善性能都采用某种预读技术。当检测到正进行顺序读取时,系统就视图读入比应用所要求的更多的数据,并假想应用很快就会读取这些数据。预读的效果从图3-6中可以看出,缓冲区长度小至32字节时的始终时间与拥有较大缓冲区长度时的时钟时间几乎一样。
原子操作
标准io库
二、Java IO模型
用户程序发起io调用时,会向操作系统发起系统调用,操作系统负责将数据从磁盘/网卡中把数据复制到内核缓冲区,然后再从内核缓冲区复制到用户进程缓冲区,对于Java程序来说,还需要复制到VM堆中。为什么没有直接复制到JVM堆中呢?因为JVM有自动垃圾回收机制,可能一开始准备复制数据的时候,这块内存时可用的,但是在复制过程中,可能会不可用。
一、五种io模型:
- 阻塞io
同步阻塞IO模型中,应用程序发起read调用后,会一直阻塞,直到内核把数据拷贝到用户空间。
在客户端连接数量不高的情况下,是没问题的。但是,当面对十万甚至百万级连接的时候,传统的 BIO 模型是无能为力的。因此,我们需要一种更高效的 I/O 处理模型来应对更高的并发量。
- 同步非阻塞io
在同步非阻塞IO中,在数据准备好之前,用户程序会不断进行I/O系统调用轮询内核是否把数据就绪了,在内核数据就绪后的拷贝数据过程中,依然是阻塞的。通过轮询的操作,可以避免一直阻塞,但是系统调用的成本也非常高。
-
多路复用io
因为同步非阻塞io采用轮询的方式系统调用导致浪费,又引入了多路复用的模型。
在多路复用中,用户程序首先发出select调用,询问内核数据是否就绪,等内核数据就绪后,会返回给用户程序ready,然后用户程序发起read调用读取数据,但是在拷贝数据的这个过程中依然是阻塞的。
本质上就是有一个内核线程来同时监视多个文件描述符,一旦有一个就绪,就返回。如果用户自己来实现这个线程,就得轮询read,多次陷入系统调用。
-
异步io
异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。
二、零拷贝:
三、java io
字节流,InputStream / OutputStream
底层的负责读取数据的流,然后在通过FilterInputStream的实现类添加其他功能,比如FileInputStream是基础类,他提供从文件中读取数据的基本能力,如果调用其read()方法,会默认从磁盘读取1个字节上来,这会造成多次访问磁盘导致性能下降;同时,FileInputStream也提供了read(byte b[], int off, int len))的方法,可以做到一次从offset的位置读取读取len字节的方法到buf数组中,但是如果用户直接用这个方法的话,需要自己管理buf和offset,导致重复造轮子;所以BufferedInputStream替我们做了这个事情,它负责管理buf和offset,从而做到一次读取多个字节,提升性能。
字符流,Reader / Writer
设计Reader和Writer继承层次结构主要是为了国际化。老的I/O流继承层次结构仅支持8位字节流,并且不能很好地处理16位的Unicode字符。Reader和Writer的使用方法和Stream基本一样。
transferTo
内存映射io
多路复用:
NIO的transferTo vs 内存映射io:
NIO(New IO)是Java提供的一组高性能的I/O API,其中包括了transferTo
和内存映射IO(Memory-Mapped IO)两种方式。
transferTo
是NIO提供的一种高性能的文件传输方式,它允许将文件从一个通道传输到另一个通道,通常用于实现文件复制、网络传输等场景。使用transferTo
时,数据被直接从源通道传输到目标通道,避免了中间缓冲区的拷贝,从而提高了传输效率。
内存映射IO则是将文件直接映射到内存中,通过对内存的直接操作来进行读写操作。当文件被映射到内存中时,文件的内容就被直接映射到了内存中,这样就可以直接在内存中读写文件,避免了数据的拷贝,从而提高了IO效率。
在使用NIO时,通常可以根据具体场景选择transferTo
或内存映射IO。如果是对大文件进行传输或复制,可以选择transferTo
,**而如果是对文件进行随机访问或者操作数据时,则可以选择内存映射IO。**需要注意的是,内存映射IO使用内存比较多,因此在对大文件进行操作时需要注意内存的使用情况,避免出现内存溢出等问题。
使用NIO的内存映射IO方式进行读写操作,通常只需要进行一次数据拷贝。
内存映射IO使用的是内核缓冲区而不是用户进程缓冲区。
内存映射IO通过将文件内容映射到内存中来进行读写操作,因此内存映射IO使用的是内核缓冲区而不是用户进程缓冲区。具体来说,内存映射IO使用mmap()
系统调用将文件内容映射到内存中,这个过程中数据是从磁盘读入内核缓冲区中的,然后将内核缓冲区的数据映射到进程的虚拟地址空间中,最后进程直接通过访问内存来进行读写操作。
使用内核缓冲区进行IO操作,可以避免用户缓冲区和内核缓冲区之间的数据拷贝,从而提高IO效率。此外,内存映射IO还可以利用虚拟内存技术,将文件映射到进程的虚拟地址空间中,使得进程可以像访问内存一样快速地访问文件数据,也能够提高IO效率。