Java中的零拷贝

前言:

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

在这里插入图片描述

传统的IO拷贝

首先将硬盘上的数据拷贝到内核,然后在经过CPU拷贝将数据从内核拷贝到应用程序内存(用户态),在应用程序内存,用户可以对数据进行操作修改等,然后在经过CPU拷贝将数据从用户缓冲区拷贝到socket缓冲区,然后在经过DMA拷贝,将数据从socket缓冲区拷贝到网卡。
那么经过以上的步骤,完成了将数据从本地硬盘传输到网络上的过程,
那这个过程数据的拷贝经过了:
硬盘—>内核—>应用程序内存(可以理解为用户态,相当于jvm中)—>socket缓冲区—>网卡,这4次拷贝的过程。
上下文的切换经过了:用户态–>内核态–>用户态 这三次上下文切换
在这里插入图片描述

mmap:

首先将硬盘上面的数据数据拷贝到内核,(因为mmap技术,做到了内核态数据和用户态数据共享,此时不需要将数据从内核拷贝到用户态,用户态也可以对数据进行修改),再将内核态的数据经过CPU拷贝,拷贝到socket缓冲区,在经过DMA拷贝,将socket缓冲区的数据拷贝到协议栈;
那经过了mmap的优化后,数据的读写少了一次拷贝的过程,但是mmap还不是真正意思上的零拷贝,因为它还是进行了拷贝
那这个过程数据的拷贝经过了:
硬盘—>内核—>socket缓冲区—>网卡,这3次拷贝的过程。
上下文的切换经过了:用户态–>内核态–>用户态 这三次上下文切换

在这里插入图片描述

sendFile

sendFile是Linux2.1版本提供的函数,基本的原理就是数据根本不经过用户态,直接从内核缓冲区进入到SocketBuffer,同时,由于和用户态完全无关,就减少了一次上下文切换;
过程:
首先将硬盘上面的数据经过DMA拷贝(直接内存拷贝),将数据拷贝到内核,再将内核态的数据经过CPU拷贝,拷贝到socket缓冲区,再由DMA拷贝将数据从socket缓冲区拷贝到协议栈。
目前来看sendFile和mmap拷贝是差不多的,但是sendFile比mmap拷贝少了一次上下文的切换,
但是就目前看来,这两种拷贝都没有达到真正的零拷贝。

在这里插入图片描述

sendFile(增强)

在Linux2.4的版本中,对sendFile做了一些修改,真正实现了零拷贝;
过程:
首先将硬盘上面的数据拷贝到内核,此时在经过CPU拷贝将数据的描述信息拷贝到socket缓冲区(注意:此时拷贝的是数据的描述信息,数据量很小,所以此次拷贝可以忽略不记),然后此时数据其实还是在内核的(因为刚刚的CPU拷贝只是拷贝了一些数据描述),再将数据从内核直接拷贝到网卡。
在这个版本中,sendFile就实现了真正的零拷贝,虽然还是经过了一次CPU的拷贝,但是数据量很小,所以可以忽略不记。

Java+NIO实现零拷贝

在java中可以使用:
fileChannel.transferTo()
和 fileChannel.map() 等函数来实现零拷贝

其中:
mmap适合小数据量的传输, RocketMQ使用的就是mmap。
sendFile 适合大数据量的传输,Kafka使用的就是sendFile。

Java 实现 mmap示例

  package org.xhs.json;

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

/**
 * @Author: hu.chen
 * @Description:
 * @DateTime: 2022/4/29 1:29 AM
 **/
public class MmpTest {


    public static void main(String[] args) {
        try {
            // 获取文件
            FileChannel readChannel = FileChannel.open(Paths.get("C://test/1.txt"), StandardOpenOption.READ);
            MappedByteBuffer data = readChannel.map(FileChannel.MapMode.READ_ONLY, 0, readChannel.size());
            FileChannel writeChannel = FileChannel.open(Paths.get("E://test1/3.txt"), StandardOpenOption.WRITE, StandardOpenOption.CREATE);
            //数据传输
            writeChannel.write(data);
            readChannel.close();
            writeChannel.close();
        }catch (Exception e){
            System.out.println(e.getMessage());
        }
    }
}

Java实现sendFile示例:

示例1:

package org.xhs.json;

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

/**
 * @Author: hu.chen
 * @Description:
 * @DateTime: 2022/5/11 12:45 PM
 **/
public class SendfileTest {

    public static void main(String[] args) {
        try {
            FileChannel readChannel = FileChannel.open(Paths.get("C://test/1.txt"), StandardOpenOption.READ);
            long len = readChannel.size();
            long position = readChannel.position();
            FileChannel writeChannel = FileChannel.open(Paths.get("E://test1/3.txt"), 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();
        }
    }
  • 6
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值