Java零拷贝技术实战

文章目录

  • 引入
  • 传统IO
  • 内存映射mmap
  • 文件描述符sendFile
  • 测试
  • 总结

引入

为什么要使用零拷贝技术?
传统写入数据需要4次拷贝,如下图:
在这里插入图片描述

传统IO

import java.io.*;
import java.net.Socket;public class TranditionIOClient {private static final int PORT = 8888;private final static String FILE_NAME = "D:\\test.mp4";// 接收缓冲区大小private static final int BUFFER_SIZE = 1024;public static void main(String[] args) throws Exception {try (Socket socket = new Socket("localhost", PORT);InputStream inputStream = new FileInputStream(FILE_NAME);DataOutputStream dos = new DataOutputStream(socket.getOutputStream());) {byte[] buffer = new byte[BUFFER_SIZE];long readCount = 0;long total = 0;long startTime = System.currentTimeMillis();// 读取文件:从硬盘读取到内存,发生2次copy(DMA拷贝和CPU拷贝)while ((readCount = inputStream.read(buffer)) >= 0) {total += readCount;// 网络发送:从内存到网卡,发生2次copy(DMA拷贝和CPU拷贝)dos.write(buffer, 0, (int) readCount);}System.out.println("TranditionIOClient发送总字节数:" + total + ",耗时:" + (System.currentTimeMillis() - startTime) + " ms");} catch (IOException e) {e.printStackTrace();}}
}

内存映射mmap

在这里插入图片描述

import java.io.FileInputStream;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.SocketChannel;public class MmapClient {private static final int PORT = 8888;private final static String FILE_NAME = "D:\\test.mp4";public static void main(String[] args) throws Exception {try (SocketChannel socketChannel = SocketChannel.open();FileChannel fileChannel = new FileInputStream(FILE_NAME).getChannel()) {socketChannel.connect(new InetSocketAddress("localhost", PORT));socketChannel.configureBlocking(true);long startTime = System.currentTimeMillis();// 获取文件大小long size = fileChannel.size();// 内存映射整个文件,发生3次copy(DMA拷贝和CPU拷贝)ByteBuffer buffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, size);// 发送文件while (buffer.hasRemaining()) {socketChannel.write(buffer);}System.out.println("MmapClient发送总字节数:" + size + ",耗时:" + (System.currentTimeMillis() - startTime) + " ms");} catch (Exception e) {e.printStackTrace();}}
}

文件描述符sendFile

在这里插入图片描述

import java.io.FileInputStream;
import java.net.InetSocketAddress;
import java.nio.channels.FileChannel;
import java.nio.channels.SocketChannel;public class SendFileClient {private static final int PORT = 8888;private final static String FILE_NAME = "D:\\test.mp4";public static void main(String[] args) throws Exception {try (SocketChannel socketChannel = SocketChannel.open();FileChannel fileChannel = new FileInputStream(FILE_NAME).getChannel()) {socketChannel.connect(new InetSocketAddress("localhost", PORT));socketChannel.configureBlocking(true);long startTime = System.currentTimeMillis();long position = 0;// 8MB,与系统缓冲区大小匹配或略小以避免问题long chunkSize = 8 * 1024 * 1024;long size = fileChannel.size();while (position < size) {long bytesRemaining = size - position;// 确保最后一次传输不会超过文件大小long count = Math.min(bytesRemaining, chunkSize);// transferTo⽅法⽤到了零拷⻉,底层是sendfile,发生2次copy(DMA拷贝)long transferCount = fileChannel.transferTo(position, count, socketChannel);if (transferCount == 0) {// 如果一次传输没有发生,可能需要检查连接是否仍然活跃或处理其他错误情况break;}position += transferCount;}System.out.println("SendFileClient发送总字节数:" + size + ",耗时:" + (System.currentTimeMillis() - startTime) + " ms");} catch (Exception e) {e.printStackTrace();}}
}

如果发送的文件不大于8M,则可以简单写,如下:

import java.io.FileInputStream;
import java.net.InetSocketAddress;
import java.nio.channels.FileChannel;
import java.nio.channels.SocketChannel;public class SendFileClient {private static final int PORT = 8888;private final static String FILE_NAME = "D:\\test.mp4";public static void main(String[] args) throws Exception {try (SocketChannel socketChannel = SocketChannel.open();FileChannel fileChannel = new FileInputStream(FILE_NAME).getChannel()) {socketChannel.connect(new InetSocketAddress("localhost", PORT));socketChannel.configureBlocking(true);long startTime = System.currentTimeMillis();// transferTo⽅法⽤到了零拷⻉,底层是sendfile,发生2次copy(DMA拷贝)long transferCount = fileChannel.transferTo(0, fileChannel.size(), socketChannel);System.out.println("SendFileClient发送总字节数:" + transferCount + ",耗时:" + (System.currentTimeMillis() - startTime) + " ms");} catch (Exception e) {e.printStackTrace();}}
}

测试

服务端代码如下:

import java.io.DataInputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;public class Server {private static final int PORT = 8888;// 接收缓冲区大小private static final int BUFFER_SIZE = 1024;public static void main(String[] args) throws Exception {try (ServerSocket ss = new ServerSocket(PORT);) {while (true) {try (Socket s = ss.accept();DataInputStream dis = new DataInputStream(s.getInputStream());) {int byteCount = 0;byte[] bytes = new byte[BUFFER_SIZE];while (true) {int readCount = dis.read(bytes, 0, BUFFER_SIZE);if (readCount == -1) {break;}byteCount = byteCount + readCount;}System.out.println("服务端接受字节数:" + byteCount + "字节");} catch (IOException e) {e.printStackTrace();}}} catch (IOException e) {e.printStackTrace();}}
}

我们都使用了test.mp4进行测试,文件大小500M,测试结果如下:

TranditionIOClient发送总字节数:524288000,耗时:9590 ms
MmapClient发送总字节数:524288000,耗时:1182 ms
SendFileClient发送总字节数:524288000,耗时:983 ms

总结

零拷贝并不是不需要拷贝,而是指计算机执行操作时,不需要将数据从内存复制到应用程序

效率高到低:sendFile>mmap>传统IO

明明传了500M的文件,但实际读出来8M?代码如下:

try (SocketChannel socketChannel = SocketChannel.open();FileChannel fileChannel = new FileInputStream(FILE_NAME).getChannel()) {socketChannel.connect(new InetSocketAddress("localhost", PORT));socketChannel.configureBlocking(true);long startTime = System.currentTimeMillis();// transferTo⽅法⽤到了零拷⻉,底层是sendfile,发生2次copy(DMA拷贝)long transferCount = fileChannel.transferTo(0, fileChannel.size(), socketChannel);System.out.println("SendFileClient发送总字节数:" + transferCount + ",耗时:" + (System.currentTimeMillis() - startTime) + " ms");
} catch (Exception e) {e.printStackTrace();
}// 输出结果:SendFileClient发送总字节数:8388608,耗时:15 ms

原因:由于操作系统的默认socket缓冲区大小限制所导致的。当使用transferTo进行大文件传输时,如果文件大小超过了操作系统为socket分配的缓冲区大小,那么transferTo可能在达到这个限制后停止,因为它试图一次性将数据从文件通道转移到socket通道,但缓冲区不足以容纳整个文件内容。

解决:分批次进行文件传输

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/12136.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【机器学习300问】81、什么是动量梯度下降算法?

动量梯度下降算法&#xff08;Momentum&#xff09;是利用指数加权移动平均的思想来实现梯度下降的算法。让我们先来回顾一下基础的梯度下降方法以及看看它有哪些不足之处。接着引出动量梯度下降算法&#xff0c;在理解了它的原理后看看它是如何规避之前方法的不足的。 如果不知…

网络安全ctf比赛_学习资源整理,解题工具、比赛时间、解题思路、实战靶场、学习路线,推荐收藏!...

对于想学习或者参加CTF比赛的朋友来说&#xff0c;CTF工具、练习靶场必不可少&#xff0c;今天给大家分享自己收藏的CTF资源&#xff0c;希望能对各位有所帮助。 CTF在线工具 首先给大家推荐我自己常用的3个CTF在线工具网站&#xff0c;内容齐全&#xff0c;收藏备用。 1、C…

使用 RyTuneX 增强您的 Windows 10 和 11 体验 – Rayen Ghanmi 的首选优化器。

&#x1f4dd; 关于 RyTuneX 是使用 WinUI 3 框架构建的尖端优化器&#xff0c;旨在增强 Windows 设备&#x1f680;的性能。 RyTuneX 专为 Windows 10 和 11 打造&#xff0c;使用户能够毫不费力地删除顽固的预装应用程序并优化系统资源&#x1f6e0;️。 &#x1f680; 功能…

微信加粉计数器

1.采用非注入式开发&#xff0c;支持无限多开 2.每个账号都有独立的分组&#xff0c;实时远程网页数据分享 3.后台功能强大&#xff0c;操作简单&#xff0c;自动去重复&#xff0c;准确计数分秒不差

Java毕业设计 基于SpringBoot vue药店管理系统

Java毕业设计 基于SpringBoot vue药店管理系统 SpringBoot 药店管理系统 功能介绍 员工 登录 个人中心 修改密码 个人信息 查看供应商信息 查看药品 查看进货 查看销售 管理员 登录 个人中心 修改密码 个人信息 供应商类型管理 供应商信用等级类型管理 药品类型管理 供应商信…

不懂数字后端Box List、Polygon的意思?

什么是BOX&#xff1f; 景芯SoC做design planning的第一步就是确定floorplan的box&#xff0c;也就是设计的区域。这个区域可以划分为三个边界&#xff0c;如下图所示&#xff1a; Die Box 最外面一圈&#xff0c;我们称为 Die Box&#xff0c;也就是用来放置 IO 单元&#x…

太阳能无人机的多元化应用

随着新能源技术的不断发展和成熟&#xff0c;太阳能在无人机的应用技术已经成熟。太阳能无人机得到了量产和广泛的应用。传统无人机相比&#xff0c;太阳能无人机无需燃油&#xff0c;运行费用低廉&#xff0c;搭载多种高科技设备&#xff0c;能够高效、多元化地采集和分析各类…

汇昌联信电商:拼多多网店好做吗?

在电子商务的海洋中&#xff0c;拼多多以其独特的团购模式和亲民策略迅速崛起&#xff0c;吸引了大批消费者和商家的目光。对于“拼多多网店好做吗?”这个问题&#xff0c;答案并非简单的是与否&#xff0c;而是需要从多个维度进行深入分析。 一、市场定位与竞争环境 拼多多定…

常见 Web 安全攻防总结

Web 安全的对于 Web 从业人员来说是一个非常重要的课题&#xff0c;所以在这里总结一下 Web 相关的安全攻防知识&#xff0c;希望以后不要再踩雷&#xff0c;也希望对看到这篇文章的同学有所帮助。今天这边文章主要的内容就是分析几种常见的攻击的类型以及防御的方法。 也许你对…

05-10 周五 推理是什么

05-10 周五 推理是什么 时间版本修改人描述2024年5月10日10:13:54V0.1宋全恒新建文档2024年5月13日11:08:42V1.0宋全恒填充了训练和推理的定义&#xff0c;并且对于推理加速的方面进行了详细的介绍 简介 最近要坐推理时的动态量化&#xff0c;因此&#xff0c;需要认真理解一下…

独立游戏《星尘异变》UE5 C++程序开发日志3——实现一个存存组件

本篇日志中&#xff0c;我将会介绍如何实现一个有格子&#xff0c;每个格子有容量的物品库存&#xff0c;如下图&#xff1a; 一.库存容器 1.储存数据的容器 库存容器最重要的目的就是存储每一种类的物品拥有的数量&#xff0c;这里我用的是哈希表&#xff1a; std::unordere…

huggingface:利用git克隆目标资源

前言 因为有很多模型资源都被放在了huggingface上&#xff0c;为了下载它们&#xff0c;着实让一个不懂git的人犯了难&#xff0c;绕了很多远路&#xff0c;甚至将不需要解决的问题也都拿上了台面&#xff0c;因此我将在本篇博客中记载一些关于【huggingface】中利用git克隆目标…

【c++】全面理解C++多态:虚函数表深度剖析与实践应用

&#x1f525;个人主页&#xff1a;Quitecoder &#x1f525;专栏&#xff1a;c笔记仓 朋友们大家好&#xff0c;通过本篇文章&#xff0c;来详细理解多态的内容 目录 1.多态的定义及实现1.1多态的构成条件1.2虚函数的重写1.3 C11 override 和 final1.4重载、覆盖(重写)、隐藏…

鸿蒙内核源码分析 (内核启动篇) | 从汇编到 main ()

这应该是系列篇最难写的一篇&#xff0c;全是汇编代码&#xff0c;需大量的底层知识&#xff0c;涉及协处理器&#xff0c;内核镜像重定位&#xff0c;创建内核映射表&#xff0c;初始化 CPU 模式栈&#xff0c;热启动&#xff0c;到最后熟悉的 main() 。 内核入口 在链接文件…

在k8s中安装Grafana并对接Prometheus,实现k8s集群监控数据的展示

&#x1f407;明明跟你说过&#xff1a;个人主页 &#x1f3c5;个人专栏&#xff1a;《Grafana&#xff1a;让数据说话的魔术师》 &#x1f3c5; &#x1f516;行路有良友&#xff0c;便是天堂&#x1f516; 目录 一、引言 1、Grafana简介 2、Grafana的重要性与影响力 …

强化训练:day9(添加逗号、跳台阶、扑克牌顺子)

文章目录 前言1. 添加逗号1.1 题目描述2.2 解题思路2.3 代码实现 2. 跳台阶2.1 题目描述2.2 解题思路2.3 代码实现 3. 扑克牌顺子3.1 题目描述3.2 解题思路3.3 代码实现 总结 前言 1. 添加逗号   2. 跳台阶   3. 扑克牌顺子 1. 添加逗号 1.1 题目描述 2.2 解题思路 我的写…

STM32学习和实践笔记(28):printf重定向实验

1.printf重定向简介 在C语言中printf函数里&#xff0c;默认输出设备是显示器&#xff0c;如果想要用这个函数将输出结果到串口或者LCD上显示&#xff0c;就必须重定义标准库函数里中printf函数调用的与输出设备相关的函数。 比如要使用printf输出到串口&#xff0c;需要先将f…

linux 任务管理(临时任务定时任务) 实验

目录 任务管理临时任务管理周期任务管理 任务管理 临时任务管理 执行如下命令添加单次任务&#xff0c;输入完成后按组合键Ctrl-D。 [rootopenEuler ~]# at now5min warning: commands will be executed using /bin/sh at> echo "aaa" >> /tmp/at.log at&g…

J-STAGE (日本电子科学与技术信息集成)数据库介绍及文献下载

J-STAGE (日本电子科学与技术信息集成)是日本学术出版物的平台。它由日本科学技术振兴机构&#xff08;JST&#xff09;开发和管理。该系统不仅包括期刊&#xff0c;还有论文集&#xff0c;研究报告、技术报告等。文献多为英文&#xff0c;少数为日文。目前网站上所发布的内容来…

使用Vue调用ColaAI Plus大模型,实现聊天(简陋版)

首先去百度文心注册申请自己的api 官网地址&#xff1a;LuckyCola 注册点开个人中心 查看这个文档自己申请一个ColaAI Plus定制增强大模型API | LuckyColahttps://luckycola.com.cn/public/docs/shares/api/colaAi.html来到vue的页面 写个样式 <template><Header …