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; 功能…

java实现音频、视频离开页面存储收听记录、观看记录

说明:本文是既《SpringBoot+thymeleaf完成视频记忆播放功能》的续写,如有兴趣可点击链接查看。 一、功能背景描述说明: 1、在手机浏览器离开时将看到的视频记录、视频时长和音频的收听记录、收听时长存入数据库记录; 2、用户通过将观看、收听记录导出,能够看到是谁看了哪些…

微信加粉计数器

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;能够高效、多元化地采集和分析各类…

flutter 禁止横屏设置

1.Flutter 设置 在 main 函数 加载app前添加以下代码 SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp,DeviceOrientation.portraitDown,]) 添加后的结果 void main() async {WidgetsBinding widgetsBinding WidgetsFlutterBinding.ensureInitializ…

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

在电子商务的海洋中&#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重载、覆盖(重写)、隐藏…

wireshark协议大致过滤规则

参考链接&#xff1a;真保姆链接 1、比较操作符 等于 &#xff01;不等于 >大于 <小于 >大于等于 <小于等于 2、协议类型 直接在Filter框中直接输入协议名即可。注意&#xff1a;协议名称需要输入小写。 tcp&#xff0c;只显示TCP协议的数据包列表udp&#xff0c…

鸿蒙内核源码分析 (内核启动篇) | 从汇编到 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 解题思路 我的写…

【Vue】vue中动态样式绑定

在Vue中&#xff0c;可以使用动态样式绑定来根据数据的变化来动态修改元素的样式。动态样式绑定可以通过以下几种方式实现&#xff1a; 对象语法 <template><div :style"dynamicStyles"></div> </template><script> export default {…