I/O操作 与 零拷贝原理

1、输入流

InputStream、InputStreamReader、BufferedReader

2、输出流

OutputStream、OutputStreamWriter、BufferedWriter

3、传统IO操作:

        基于传统的IO方式,底层实际上通过调用read()和write()来实现。
        通过read()把数据从硬盘读取到内核缓冲区,再复制到用户缓冲区;然后再通过write()写入到socket缓冲区,最后写入网卡设备。

        整个过程发生了4次用户态和内核态的上下文切换和4次拷贝,具体流程如下:
1、用户进程通过read()方法向操作系统发起调用,此时【上下文从用户态转向内核态】
2、DMA控制器把数据从硬盘中拷贝到读缓冲区
3、CPU把读缓冲区数据拷贝到应用缓冲区,【上下文从内核态转为用户态】,read()返回
4、用户进程通过write()方法发起调用,【上下文从用户态转为内核态】
5、CPU将应用缓冲区中数据拷贝到socket缓冲区
6、DMA控制器把数据从socket缓冲区拷贝到网卡,【上下文从内核态切换回用户态】,write()返回

4、零拷贝

        在Java程序中,零拷贝技术分为两种:mmap(内存映射)和sendFile,首先要了解零拷贝的概念:所谓的零拷贝不是不拷贝,而是不经过CPU拷贝,它还是需要拷贝的(比如将数据从硬盘拷贝到内核态),这个零拷贝是从操作系统(CPU)的角度看的。

        零拷贝技术是指计算机执行操作时,CPU不需要先将数据从某处内存复制到另一个特定区域,这种技术通常用于通过网络传输文件时节省CPU周期和内存带宽。
        那么对于零拷贝而言,并非真的是完全没有数据拷贝的过程,只不过是减少用户态和内核态的切换次数以及CPU拷贝的次数。

4.1、mmap

        mmap+write简单来说就是使用mmap替换了read+write中的read操作,减少一次CPU拷贝。
        mmap主要实现方式是将读缓冲区的地址和用户缓冲区的地址进行映射,内核缓冲区和应用缓冲区共享,从而减少了从读缓冲区到用户缓冲区的一次CPU拷贝。

整个过程发生了4次用户态和内核态的上下文切换和3次拷贝,具体流程如下:
1、用户进程通过mmap()方法向操作系统发起调用,【上下文从用户态转向内核态】
2、DMA控制器把数据从硬盘中拷贝到读缓冲区
3、【上下文从内核态转为用户态】,mmap调用返回
4、用户进程通过write()方法发起调用,【上下文从用户态转为内核态】
5、CPU将读缓冲区中数据拷贝到socket缓冲区
6、DMA控制器把数据从socket缓冲区拷贝到网卡,【上下文从内核态切换回用户态】,write()返回

mmap的方式节省了一次CPU拷贝,同时由于用户进程中的内存是虚拟的,只是映射到内核的读缓冲区,所以可以节省一半的内存空间,比较适合大文件的传输。

import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;

public class MmapTest {

    public static void main(String[] args) {
        try {
            // 获取文件
            FileChannel readChannel = FileChannel.open(Paths.get("path1"), 
                                                       StandardOpenOption.READ);
            //MapMode:映射的模式,可选项包括:READ_ONLY,READ_WRITE,PRIVATE;
            //Position:从哪个位置开始映射,字节数的位置;
            //Size:从position开始向后多少个字节;
            MappedByteBuffer data = readChannel.map(FileChannel.MapMode.READ_ONLY, 
                                                    0, 
                                                    1024 * 1024 * 40);
            FileChannel writeChannel = FileChannel.open(Paths.get("path2"), 
                                                        StandardOpenOption.WRITE, 
                                                        StandardOpenOption.CREATE);
            //数据传输
            writeChannel.write(data);
            readChannel.close();
            writeChannel.close();
        }catch (Exception e){
            System.out.println(e.getMessage());
        }
    }
}

4.2、sendFile

        sendfile系统调用在内核版本2.1中被引入,通过使用sendfile数据可以直接在内核空间进行传输,因此避免了用户空间和内核空间的拷贝,目的是简化通过网络在两个通道之间进行的数据传输过程。相比mmap来说,sendfile同样减少了一次CPU拷贝,而且还减少了2次上下文切换。

整个过程发生了2次用户态和内核态的上下文切换和3次拷贝,具体流程如下:
1、用户进程通过sendfile()方法向操作系统发起调用,【上下文从用户态转向内核态】
2、DMA控制器把数据从硬盘中拷贝到读缓冲区
3、CPU将读缓冲区中数据拷贝到socket缓冲区
4、DMA控制器把数据从socket缓冲区拷贝到网卡,【上下文从内核态切换回用户态】,sendfile调用返回

sendfile方法IO数据对用户空间完全不可见,所以只能适用于完全不需要用户空间处理的情况,比如静态文件服务器。

import java.nio.channels.FileChannel;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;

public class SendfileTest {

    //实例1
    public static void main(String[] args) {
        try {
            FileChannel readChannel = FileChannel.open(Paths.get("path1"), StandardOpenOption.READ);
            long len = readChannel.size();
            long position = readChannel.position();
            FileChannel writeChannel = FileChannel.open(Paths.get("path2"), 
                                                       StandardOpenOption.WRITE, 
                                                       StandardOpenOption.CREATE);
            //数据传输
//开始发送数据:在Java中使用零拷贝技术调用transferTo方法,这个方法底层使用了零拷贝技术
// 在Linux系统下 使用transferTo 方法,没有文件大小限制,可以将文件调用一次transferTo方法即可传输完成
//但是在Windows系统下调用一次transferTo 方法,最多只能发送 8m 的数据,所以需要将文件进行分段传输
            // transferTo 参数介绍:
            //第一个参数:从文件的哪里开始读取
            //第二个参数:读取多少字节
            //第三个参数:将读取的字节,放入需要写入的Channel中
            readChannel.transferTo(position, len, writeChannel);
            readChannel.close();
            writeChannel.close();
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }

    }
    
    //实例2
    public void writeHtml(File file) {
        if (file.exists() && file.isFile()) {
            try {
                System.err.println("文件存在");
                //文件存在进行输出,此处使用零拷贝技术
                //获取到该 channel 关联的 缓存Buffer
                ByteBuffer writeBuffer = ByteBuffer.allocate(1024);
                //得到一个文件通道
                FileChannel fileChannel = new FileInputStream(file).getChannel();
                writeBuffer.put(HttpProtocolUtil.sendHead(fileChannel.size(),"200"));
                writeBuffer.flip();
                channel.write(writeBuffer);
//开始发送数据:在Java中使用零拷贝技术调用transferTo方法,这个方法底层使用了零拷贝技术
// 在Linux系统下 使用transferTo 方法,没有文件大小限制,可以将文件调用一次transferTo方法即可传输完成
//但是在Windows系统下调用一次transferTo 方法,最多只能发送 8m 的数据,所以需要将文件进行分段传输
                // transferTo 参数介绍:
                // 第一个参数:从文件的哪里开始读取
                // 第二个参数:读取多少字节
                // 第三个参数:将读取的字节需要放入的SocketChannel
                long count = fileChannel.transferTo(0, fileChannel.size(), channel);
                System.err.println("传输的总的字节大小:"+count);
                //关闭通道
                close();
            } catch (Exception e) {
                e.printStackTrace();
                System.err.println("写出静态资源失败");
            }
        } else {
            doWrite(HttpProtocolUtil.send404("404 资源未找到"));
            //关闭通道
            close();
        }
    }
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值