当前位置: 首页 > news >正文

Java实现HTTP的上传与下载

相信很多人对于java文件下载的过程都存在一些疑惑,比如下载上传文件会不会占用vm内存,上传/下载大文件会不会导致oom。下面从字节流的角度看下载/上传的实现,可以更加深入理解文件的上传和下载功能。

文件下载

首先明确,文件下载不仅仅只有下载方,还有服务端也就是返回文件的服务器
那么看一个简易文件服务器返回下载的文件。

服务端

这里是使用springMvc实现

    @GetMapping("download")public void downFile(HttpServletResponse response) throws IOException {response.setContentType("application/octet-stream");response.setHeader("Content-Disposition", "attachment; filename=" + "test.jhprof");File file = new File("D:\\heap\\heapDump.hprof");InputStream in = new FileInputStream(file);OutputStream out = response.getOutputStream();byte[] buffer = new byte[1024];int len = 0;while ((len = in.read(buffer)) > 0) {out.write(buffer, 0, len);}in.close();out.close();}

这里每次从文件流中读取1024个字节输出,是因为如果读取太多字节会给内存造成压力,我们这里使用的是java的堆内存。如果直接完整读取整个文件,那么可以导致oom。

java.lang.OutOfMemoryError: Java heap space

客户端

        URL url = new URL("http://localhost:8062/fallback/download");URLConnection conn = url.openConnection();InputStream in = conn.getInputStream();FileOutputStream fileOutputStream = new FileOutputStream("D:\\tmp\\a.hrpof");byte[] buffer = new byte[1024];int len = 0;while ((len = in.read(buffer)) > 0) {fileOutputStream.write(buffer, 0, len);}in.close();fileOutputStream.close();

同样,在下载文件时也需要注意,不要一次读取特别多的字节数
测试过程发现由于TCP发送缓冲区和接受缓冲区有限,当缓冲区满之后就会阻塞,例如下载方的速度满,服务端的文件不断写到缓冲区,缓冲区满了,就无法继续写入,那么就会导致在执行write方法时暂时阻塞。等到接收端接受到数据了,才能继续写入。

文件上传

服务端

http是支持多个文件进行上传的,文件数据都在请求体中,多个文件之间可以通过分隔符区分
例如上传两个文本文件
请求大概长这样

1.HTTP上传method=post,enctype=multipart/form-data;
2.计算出所有上传文件的总的字节数作为Content-Length的值
3.设置
Content-Type:multipart/form-data; boundary=----WebKitFormBoundaryJ9RUA0QCk13RaoAp
4.多个文件数据:请求体
------WebKitFormBoundaryJ9RUA0QCk13RaoAp
Content-Disposition: form-data; name="pic"; filename="thunder.gif"
Content-Type: image/gif
这中间是文件的二进制数据------WebKitFormBoundaryJ9RUA0QCk13RaoAp
Content-Disposition: form-data; name="pic"; filename="uuu.gif"
Content-Type: image/gif
这中间是文件的二进制数据
------WebKitFormBoundaryJ9RUA0QCk13RaoAp--

服务端使用springMvc接收上传文件的写法(多个文件)

 @RequestMapping(method = RequestMethod.POST,value = "/uploadModel")public void uploadModel(@RequestPart(value = "file", required = true) List<MultipartFile> file, @RequestParam Integer type) {··········}

这样就可以直接获取到上传的文件。
具体接受过程还要看springMvc的实现
在doDispatch方法中会是否按照文件进行处理
判断方式也很简单检查请求头的multipart

@Overridepublic boolean isMultipart(HttpServletRequest request) {return StringUtils.startsWithIgnoreCase(request.getContentType(), "multipart/");}

如果是文件类型,那么就要通过IO流将文件下载到本地
springMvc的大致实现如下
原代码位置org.apache.tomcat.util.http.fileupload.FileUploadBase#parseRequest

           FileItemIterator iter = getItemIterator(ctx);FileItemFactory fileItemFactory = Objects.requireNonNull(getFileItemFactory(), "No FileItemFactory has been set.");final byte[] buffer = new byte[Streams.DEFAULT_BUFFER_SIZE];//8KB的字节数组用于读取字节流while (iter.hasNext()) {final FileItemStream item = iter.next();// Don't use getName() here to prevent an InvalidFileNameException.final String fileName = ((FileItemStreamImpl) item).getName();FileItem fileItem = fileItemFactory.createItem(item.getFieldName(), item.getContentType(),item.isFormField(), fileName);items.add(fileItem);try {Streams.copy(item.openStream(), fileItem.getOutputStream(), true, buffer);} catch (FileUploadIOException e) {throw (FileUploadException) e.getCause();} catch (IOException e) {throw new IOFileUploadException(String.format("Processing of %s request failed. %s",MULTIPART_FORM_DATA, e.getMessage()), e);}final FileItemHeaders fih = item.getHeaders();fileItem.setHeaders(fih);}successful = true;return items;

根据分隔符找出上传的多个文件进行读取字节流,同时创建本地文件,写入到本地文件,这里循环通过8kb数组读取到内存,再写到文件,是为了防止文件过大造成占用内存大。

public static long copy(InputStream inputStream,OutputStream outputStream, boolean closeOutputStream,byte[] buffer)throws IOException {OutputStream out = outputStream;InputStream in = inputStream;try {long total = 0;for (;;) {int res = in.read(buffer);if (res == -1) {break;}if (res > 0) {total += res;if (out != null) {out.write(buffer, 0, res);}}}if (out != null) {if (closeOutputStream) {out.close();} else {out.flush();}out = null;}in.close();in = null;return total;} finally {IOUtils.closeQuietly(in);if (closeOutputStream) {IOUtils.closeQuietly(out);}}}

从这里我们也能看出来,http请求不并不是tomcat服务器接收进完全接收的,而是先接收请求头进行就开始进行处理了,至于后面要不要读取请求提,如何读取,就要看程序员的代码了,这也是程序员可以控制的。

客户端

文件上传的客户端逻辑比较复杂

package javaio;import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;/*** @author liuxishan 2023/9/3*/public class FileUpload  {public static void main(String[] args) {String reslut = null;Map<String, File> files = new HashMap() {{put("0.png", new File("C:\\Users\\lxs\\Desktop\\0.png"));put("1.jpg", new File("C:\\Users\\lxs\\Desktop\\1.png"));put("big.herof", new File("D:\\heap\\heapDump.hprof"));}};try {String BOUNDARY = java.util.UUID.randomUUID().toString();String PREFIX = "--", LINEND = "\r\n";String MULTIPART_FROM_DATA = "multipart/form-data";String CHARSET = "UTF-8";URL uri = new URL("http://localhost:8062/fallback/uploadModel?type=1");HttpURLConnection conn = (HttpURLConnection) uri.openConnection();//   conn.setChunkedStreamingMode(0);conn.setReadTimeout(100 * 1000);conn.setDoInput(true);// 允许输入conn.setDoOutput(true);// 允许输出conn.setUseCaches(false);conn.setRequestMethod("POST"); // Post方式conn.setRequestProperty("connection", "keep-alive");conn.setRequestProperty("Charsert", "UTF-8");conn.setRequestProperty("Content-Type", MULTIPART_FROM_DATA  + ";boundary=" + BOUNDARY);conn.connect();// 首先组拼文本类型的参数StringBuilder sb = new StringBuilder();OutputStream outStream = conn.getOutputStream();outStream.flush();// 发送文件数据if (files != null)
//		         for (Map.Entry<String, File> file : files.entrySet()) {for (String key : files.keySet()) {StringBuilder sb1 = new StringBuilder();sb1.append(PREFIX);sb1.append(BOUNDARY);sb1.append(LINEND);sb1.append("Content-Disposition: form-data; name=\"file\"; filename=\""   + key + "\"" + LINEND);sb1.append("Content-Type: multipart/form-data; charset="  + CHARSET + LINEND);sb1.append(LINEND);outStream.write(sb1.toString().getBytes());File valuefile = files.get(key);InputStream is = new FileInputStream(valuefile);byte[] buffer = new byte[1024];int len = 0;while ((len = is.read(buffer)) != -1) {outStream.write(buffer, 0, len);}is.close();outStream.write(LINEND.getBytes());}// 请求结束标志byte[] end_data = (PREFIX + BOUNDARY + PREFIX + LINEND).getBytes();outStream.write(end_data);outStream.flush();// 得到响应码
//		     success = conn.getResponseCode()==200;InputStream in = conn.getInputStream();InputStreamReader isReader = new InputStreamReader(in);BufferedReader bufReader = new BufferedReader(isReader);String line = null;reslut = "";while ((line = bufReader.readLine()) != null)reslut += line;outStream.close();conn.disconnect();} catch (Exception e) {e.printStackTrace();}}}

这里需要注意的是在使用HttpURLConnection 上传大文件时,出现内存溢出的错误,这让我产生了错觉,输入和输出流咋会暂用内存,不就是一个数据传送的管道么,都没有把数据读取到内存中,为撒会报错。。。然后就纠结了。。。
不过实在与原来的经验相违背,然后写了一个示例直接从file中读出然后写入到输出流中,发现并没有问题
查HttpURLConnection api发现其有缓存机制,数据并没有实时发送到网络,而是先缓存再发送,导致内存溢出。
解决办法:
httpConnection.setChunkedStreamingMode(0);
//不使用HttpURLConnection的缓存机制,直接将流提交到服务器上。
需要注意的是我们经常使用的hutool的http也存在这个问题
如果不指定chunkedStreamingMode也会出现oom的问题
对于大文件可以这么进行指定。

  HttpResponse response = HttpUtil.createPost("http://localhost:8062/fallback/uploadModel?type=1").setChunkedStreamingMode(0).form("file", new File("D:\\heap\\heapDump.hprof")).execute();

相关文章:

Java实现HTTP的上传与下载

相信很多人对于java文件下载的过程都存在一些疑惑&#xff0c;比如下载上传文件会不会占用vm内存&#xff0c;上传/下载大文件会不会导致oom。下面从字节流的角度看下载/上传的实现&#xff0c;可以更加深入理解文件的上传和下载功能。 文件下载 首先明确&#xff0c;文件下载…...

VPG算法

VPG算法 前言 首先来看经典的策略梯度REINFORCE算法&#xff1a; 在REINFORCE中&#xff0c;每次采集一个episode的轨迹&#xff0c;计算每一步动作的回报 G t G_t Gt​&#xff0c;与动作概率对数相乘&#xff0c;作为误差反向传播&#xff0c;有以下几个特点&#xff1a; …...

docker 笔记5:redis 集群分布式存储案例

尚硅谷Docker实战教程&#xff08;docker教程天花板&#xff09;_哔哩哔哩_bilibili 目录 1.cluster(集群)模式-docker版哈希槽分区进行亿级数据存储 1.1面试题 1.1.1 方案1 哈希取余分区 1.1.2 方案2 一致性哈希算法分区 原理 优点 一致性哈希算法的容错性 一致性…...

【Vue2】 axios库

网络请求库-axios库 认识Axios库为什么选择Axios库安装Axios axios发送请求常见的配置选项简单请求可以给Axios设置公共的基础配置发送多个请求 axios创建实例为什么要创建axios的实例 axios的拦截器请求拦截器响应拦截器 axios请求封装 认识Axios库 为什么选择Axios库 在游览…...

云计算 - 百度AIStudio使用小结

云计算 - 百度AIStudio使用小结 前言 本文以ffmpeg处理视频为例&#xff0c;小结一下AI Studio的使用体验及一些避坑技巧。 算力获得 免费的算力获得方式为&#xff1a;每日登录后运行一个项目&#xff08;只需要点击运行&#xff0c;不需要真正运行&#xff09;即可获得8小…...

刷新你对Redis持久化的认知

认识持久化 redis是一个内存数据库&#xff0c;数据存储到内存中。而内存的数据是不持久的&#xff0c;要想做到持久化&#xff0c;就需要让redis把数据存储到硬盘上。因此redis既要在内存上存储一份数据&#xff0c;还要在硬盘上存储一份数据。这样这两份数据在理论上是完全相…...

Greenplum-最佳实践小结

注&#xff1a;本文翻译自https://docs.vmware.com/en/VMware-Greenplum/7/greenplum-database/best_practices-logfiles.html 数据模型 Greenplum数据库是一个分析型MPP无共享数据库。该模型与高度规范化/事务性的SMP数据库明显不同。Greenplum数据库使用适合MPP分析处理的非…...

从Gamma空间改为Linear空间会导致性能下降吗

1&#xff09;从Gamma空间改为Linear空间会导致性能下降吗 2&#xff09;如何处理没有使用Unity Ads却收到了GooglePlay平台的警告 3&#xff09;C#端如何处理xLua在执行DoString时候死循环 4&#xff09;Texture2DArray相关 这是第350篇UWA技术知识分享的推送&#xff0c;精选…...

双轨制的发展,弊端和前景

双轨制是一种经济体制&#xff0c;指两种不同的规则或机制并行运行&#xff0c;以适应不同的市场或客户需求。双轨制最早出现在中国的改革开放中&#xff0c;是从计划经济向市场经济过渡的一种渐进式改革方式。 双轨制的发展可以分为三个阶段&#xff1a; 第一阶段&#xff08;…...

生成对抗网络(GAN):在图像生成和修复中的应用

文章目录 什么是生成对抗网络&#xff08;GAN&#xff09;&#xff1f;GAN在图像生成中的应用图像生成风格迁移 GAN在图像修复中的应用图像修复 拓展应用领域总结 &#x1f389;欢迎来到AIGC人工智能专栏~生成对抗网络&#xff08;GAN&#xff09;&#xff1a;在图像生成和修复…...

扬杰科技携手企企通,召开SRM采购供应链协同系统项目启动会

近日&#xff0c;中国功率半导体领先企业扬州扬杰电子科技股份有限公司&#xff08;以下简称“扬杰科技”&#xff09;与企企通召开SRM采购供应链协同系统项目启动会&#xff0c;双方项目团队成员一同出席本次会议。 会上&#xff0c;双方就扬杰科技采购供应链管理平台项目的目…...

AtCoder Beginner Contest 318

目录 A - Full Moon B - Overlapping sheets C - Blue Spring D - General Weighted Max Matching E - Sandwiches F - Octopus A - Full Moon #include<bits/stdc.h> using namespace std; const int N1e65; typedef long long ll ; const int maxv4e65; typedef …...

《Python魔法大冒险》003 两个神奇的魔法工具

魔法师:小鱼,要开始编写魔法般的Python程序,我们首先需要两个神奇的工具:Python解释器和代码编辑器。 小鱼:这两个工具是做什么的? 魔法师:你可以把Python解释器看作是一个魔法棒,只要你向它说出正确的咒语,它就会为你施展魔法。 小鱼:那这个解释器和我之前用的电…...

每日一题-动态规划(从不同类型的物品中各挑选一个,使得最后花费总和等于1000)

四种类型的物品&#xff0c;每一种类型物品数量都是n&#xff0c;先要从每种类型的物品中挑选一件&#xff0c;使得最后花费总和等于1000 暴力做法10000^4 看到花费总和是1000&#xff0c;很小且固定的数字&#xff0c;肯定有玄机&#xff0c;从这里想应该是用dp&#xff0c;不…...

2023-9-3 试除法判定质数

题目链接&#xff1a;试除法判定质数 #include <iostream>using namespace std;bool is_prime(int n) {if(n < 2) return false;for(int i 2; i < n / i; i){if(n % i 0) return false;}return true; }int main() {int n;cin >> n;while(n--){int x;cin &g…...

【Apollo学习笔记】——规划模块TASK之RULE_BASED_STOP_DECIDER

文章目录 前言RULE_BASED_STOP_DECIDER相关配置RULE_BASED_STOP_DECIDER总体流程StopOnSidePassCheckClearDoneCheckSidePassStopIsPerceptionBlockedIsClearToChangeLaneCheckSidePassStopBuildStopDecisionELSE:涉及到的一些其他函数NormalizeAngleSelfRotate CheckLaneChang…...

【SpringBoot】最基础的项目架构(SpringBoot+Mybatis-plus+lombok+knife4j+hutool)

汝之观览&#xff0c;吾之幸也&#xff01; 从本文开始讲下项目中用到的一些框架和技术&#xff0c;最基本的框架使用的是SpringBoot(2.5.10)Mybatis-plus(3.5.3.2)lombok(1.18.28)knife4j(3.0.3)hutool(5.8.21),可以做到代码自动生成&#xff0c;满足最基本的增删查改。 一、新…...

RNN 单元:分析 GRU 方程与 LSTM,以及何时选择 RNN 而不是变压器

一、说明 深度学习往往感觉像是在雪山上找到自己的道路。拥有坚实的原则会让你对做出决定更有信心。我们都去过那里 在上一篇文章中&#xff0c;我们彻底介绍并检查了 LSTM 单元的各个方面。有人可能会争辩说&#xff0c;RNN方法已经过时了&#xff0c;研究它们是没有意义的。的…...

Linux音频了解

ALPHA I.MX6U 开发板支持音频&#xff0c;板上搭载了音频编解码芯片 WM8960&#xff0c;支持播放以及录音功能&#xff01; 本章将会讨论如下主题内容。 ⚫ Linux 下 ALSA 框架概述&#xff1b; ⚫ alsa-lib 库介绍&#xff1b; ⚫ alsa-lib 库移植&#xff1b; ⚫ alsa-l…...

精心整理了优秀的GitHub开源项目,包含前端、后端、AI人工智能、游戏、黑客工具、网络工具、AI医疗等等,空闲的时候方便看看提高自己的视野

精心整理了优秀的GitHub开源项目&#xff0c;包含前端、后端、AI人工智能、游戏、黑客工具、网络工具、AI医疗等等&#xff0c;空闲的时候方便看看提高自己的视野。 刚开源就变成新星的 igl&#xff0c;不仅获得了 2k star&#xff0c;也能提高你开发游戏的效率&#xff0c;摆…...

阿里开源MGeo地址匹配:零基础3步搭建,开箱即用

阿里开源MGeo地址匹配&#xff1a;零基础3步搭建&#xff0c;开箱即用 1. 为什么你需要MGeo地址匹配&#xff1f; 地址数据混乱是每个数据工程师的噩梦。同一地点在不同系统中可能有十几种写法&#xff1a;"北京市海淀区中关村大街1号"、"北京海淀中关村1号&q…...

告别环境配置劝退!跨平台研发环境搭建终极指南:从零基础到工程化落地

对于每一位开发者而言&#xff0c;研发环境是所有代码的「第一生产车间」&#xff0c;是技术成长的起点。但行业内一个非常普遍的现状是&#xff1a;超过80%的编程新手&#xff0c;在入门的第一周就会栽在环境配置上。 下载超时、权限报错、版本冲突、command not found玄学问…...

ROS中tf2坐标系命名规范详解:为什么你的/world会报Invalid argument错误

ROS中tf2坐标系命名规范详解&#xff1a;为什么你的/world会报Invalid argument错误 在ROS机器人开发中&#xff0c;坐标系转换&#xff08;tf2&#xff09;系统是构建空间感知的核心基础设施。许多开发者第一次遇到Invalid argument "/world" passed to canTransfor…...

虚幻引擎C++实战:用TSharedPtr管理资源时90%人会犯的3个内存错误

虚幻引擎C实战&#xff1a;用TSharedPtr管理资源时90%人会犯的3个内存错误 在虚幻引擎的C开发中&#xff0c;智能指针系统是资源管理的核心工具之一。TSharedPtr作为UE提供的引用计数智能指针&#xff0c;其设计初衷是为了简化内存管理&#xff0c;但实际开发中却常常成为内存泄…...

Druid连接池minIdle和maxActive参数详解:如何避免连接池耗尽问题

Druid连接池minIdle与maxActive参数深度优化指南 1. 理解连接池的核心参数 在现代企业级应用中&#xff0c;数据库连接池的性能调优往往是系统稳定性的关键所在。作为阿里巴巴开源的Druid连接池&#xff0c;其minIdle和maxActive参数的合理配置直接影响着应用的吞吐量和响应时间…...

基于Matlab的齿轮动力学仿真探索

基于matlab的齿轮动力学仿真。 该模型考虑动态啮合角和动态间隙非线性的影响&#xff0c;将扭矩直接作用于齿轮&#xff0c;并定义已知的系统参数&#xff1a;齿数、模数、转动惯量、压力角、转速等&#xff0c;输出齿轮X、Y方向的时变位移及FFT结果&#xff0c;综合变形时域图…...

74HC595驱动8位数码管实战:从查找表到动态扫描的完整流程

74HC595驱动8位数码管实战&#xff1a;从查找表到动态扫描的完整流程 在嵌入式系统开发中&#xff0c;数码管显示是最基础也最考验硬件理解能力的环节之一。记得我第一次尝试用74HC595驱动数码管时&#xff0c;被那个"看似简单却暗藏玄机"的动态扫描原理折磨了整整三…...

从文档智能处理到自动化工作流:现代开发技能的全栈实践

从文档智能处理到自动化工作流&#xff1a;现代开发技能的全栈实践 【免费下载链接】skills 本仓库包含的技能展示了Claude技能系统的潜力。这些技能涵盖从创意应用到技术任务、再到企业工作流。 项目地址: https://gitcode.com/GitHub_Trending/skills3/skills 在日常开…...

孤能子视角:“智能动力学“,AI的“自指“分析

(我将【孤能子视角:“关系“通透&#xff0c;难转译为“实体“ - CSDN App】https://blog.csdn.net/lzmtw/article/details/159602104?sharetypeblog&shareId159602104&sharereferAPP&sharesourcelzmtw&sharefromlink的内容贴给悟空浏览器的悟空AI&#xff0c…...

LangChain实战:从零构建一个联网搜索增强的RAG问答系统

1. 为什么需要联网搜索增强的RAG系统 传统的RAG&#xff08;检索增强生成&#xff09;系统有个致命伤——它只能回答知识库里已有的内容。想象一下&#xff0c;你去年精心构建了一个旅游推荐系统&#xff0c;但今年新开的网红景点它完全不知道&#xff0c;因为数据没更新。这就…...