Java如何实现零拷贝

什么是零拷贝

在操作系统中,从内核的形态区分,可以分为内核态(Kernel Space)和用户态(User Space)。

在传统的IO中,如果把数据通过网络发送到指定端的时候,数据需要经历下面的几个过程:

IO

  1. 当调用系统函数的时候,CPU执行一系列准备工作,然后把请求发送给DMA处理(DMA可以理解为专门处理IO的组件),DMA将硬盘数据通过总线传输到内存中。
  2. 当程序需要读取内存的时候,这个时候会执行CPU Copy,内存会有内核态写入用户的缓存区。
  3. 系统调用write()方法时,数据从用户态缓冲区写入到网络缓冲区(Socket Buffer), 由用户态编程内核态。
  4. 最后由DMA写入网卡驱动中,传输到网卡的驱动。

可以看到,传统的IO的读写,数据会经历4次内存的拷贝,这种拷贝拷贝会带来资源的浪费和效率的底下。


如何实现零拷贝


阅读更多

Java中实现顺序IO

顺序IO和随机IO


对于磁盘的读写分为两种模式,顺序IO和随机IO。 随机IO存在一个寻址的过程,所以效率比较低。而顺序IO,相当于有一个物理索引,在读取的时候不需要寻找地址,效率很高。

网上盗了一个图(侵权删)
IO


Java中的随机读写


在Java中读写文件的方式有很多种,先总结以下3种方法:

  1. FileWriter和FileReader

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    public static void fileWrite(String filePath, String content) {
    File file = new File(filePath);
    //创建FileWriter对象
    FileWriter writer = null;
    try {
    //如果文件不存在,创建文件
    if (!file.exists())
    file.createNewFile();
    writer = new FileWriter(file);
    writer.write(content);//写入内容
    writer.flush();
    writer.close();
    } catch (IOException e) {
    e.printStackTrace();
    }
    }

    public static void fileRead(String filePath) {
    File file = new File(filePath);
    if (file.exists()) {
    try {
    //创建FileReader对象,读取文件中的内容
    FileReader reader = new FileReader(file);
    char[] ch = new char[1];
    while (reader.read(ch) != -1) {
    System.out.print(ch);
    }
    reader.close();
    } catch (IOException ex) {
    ex.printStackTrace();
    }

    }
    }
阅读更多

网络IO模型

IO本质上是对数据缓冲区的读写,主要分为文件IO和网络IO,基本模型有很多,可以从两个方面去认识 同步和异步,阻塞和非阻塞。根据上面分类可以分为下面五类:

  1. 阻塞I/O(blocking I/O)
  2. 非阻塞I/O (nonblocking I/O)
  3. I/O复用(select 、poll和epoll) (I/O multiplexing)
  4. 信号驱动I/O (signal driven I/O (SIGIO))
  5. 异步I/O (asynchronous I/O )

阻塞IO

阻塞IO也是常说的BIO,是一个单线程的阻塞模型,在执行数据拷贝的时候,会阻塞主进程,直到数据拷贝完成。具体模式如下图:
BIO

从图中可以看出,这种模型的比较简单,及时性比较高,但是会阻塞用户进程,在使用的时候常常结合多线程或者多进程来使用。

如果在连接数比较多的情况下,多线程只能缓解,无法彻底解决。总之,多线程可以方便高效的解决小规模的服务请求,但面对大规模的服务请求,多线程模型也会遇到瓶颈。

非阻塞IO

由于BIO的执行效率和阻塞的问题,单机无法承载过多的数据处理。对于用户来说来说,其实不用等待数据的准备过程,只需要返回数据有没有准备好就行。

NIO

阅读更多

select,poll,epoll的区别

在多路复用的IO的模型中,存在三种机制,分别是selectpollepoll.为了便于理解,可以使用简单的伪代码来表示一个原始的IO的读写:

1
2
3
4
5
6
7
8
9
10
while(true)  
{
for(Stream i: streamArr)
{
if(i.isNotReady()){
continue;
}
doSomething();
}
}

select

时间复杂度O(n),它仅仅知道了,有I/O事件发生了,却并不知道是哪那几个流(可能有一个,多个,甚至全部),我们只能无差别轮询所有流,找出能读出数据,或者写入数据的流,对他们进行操作。所以select具有O(n)的无差别轮询复杂度,同时处理的流越多,无差别轮询时间就越长。 具体的伪代码如下:

1
2
3
4
5
6
7
8
9
10
11
while(true)  
{
getSelectReadyStream();//此处是同步方法,如果有准备好的数据才会向下走。
for(Stream i: streamArr)
{
if(i.isNotReady()){
continue;
}
doSomething();
}
}

select的缺点:

(1)每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大

(2)同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大

(3)select支持的文件描述符数量太小了,默认是1024

阅读更多