← 返回题目列表

文件上传的原理是什么?multipart/form-data 是怎么回事、大文件上传怎么处理?

高频 中等 第 3 / 23 题 更新于 2026/08/03
文件上传multipartMultipartFile大文件

简化版

**文件上传的核心是「HTTP 用 multipart/form-data 编码把文件(和其他表单字段)打包在请求体里传给服务端」,服务端解析这个格式取出文件。**为什么用 multipart/form-data:普通表单用 application/x-www-form-urlencoded(键值对编码)只能传文本、不能传二进制文件;multipart/form-data 把请求体分成多个「部分(part)」,每个 part 有自己的头(含字段名、文件名、类型)和内容,用「分隔符(boundary)」隔开各个 part,能同时传文件(二进制)和普通字段。Servlet 3.0+ 的处理:用 request.getParts()(或 getPart(name))取出各 part,或用 @MultipartConfig 配置;Spring MVC 更简单:用 MultipartFile 参数直接接收(@RequestParam("file") MultipartFile file),调 file.getInputStream()/transferTo(dest) 保存。大文件上传要注意① 别把整个文件读进内存(用流式处理、transferTo 边读边写);② 分片上传(前端把大文件切成小块分别上传、服务端合并,支持断点续传);③ 限制大小max-file-size)防止超大文件打垮服务。核心:文件上传用 multipart/form-data 打包(boundary 分隔 part),Spring 用 MultipartFile 接收;大文件要流式、分片、限大小。

详细版

multipart/form-data 请求体结构

Content-Type: multipart/form-data; boundary=----WebKitBoundary123

------WebKitBoundary123
Content-Disposition: form-data; name="username"     ← 普通字段

Tom
------WebKitBoundary123
Content-Disposition: form-data; name="file"; filename="a.jpg"  ← 文件字段
Content-Type: image/jpeg

<文件的二进制内容>
------WebKitBoundary123--                            ← 结束分隔符

处理方式对比

方式API
Servlet 3.0+request.getParts() / getPart(name),@MultipartConfig
Spring MVCMultipartFile(@RequestParam(“file”) MultipartFile file)
多文件MultipartFile[] / List
// Spring MVC 文件上传(最简洁)
@PostMapping("/upload")
public String upload(@RequestParam("file") MultipartFile file,
                     @RequestParam("username") String username) throws IOException {
    if (file.isEmpty()) return "空文件";
    String filename = file.getOriginalFilename();   // 原始文件名
    long size = file.getSize();                      // 大小
    // ★ 流式保存(别读进内存)
    file.transferTo(new File("/data/" + filename));  // 直接保存到磁盘
    // 或 file.getInputStream() 流式处理
    return "上传成功";
}

// 配置上传大小限制(application.yml)
// spring.servlet.multipart.max-file-size=10MB
// spring.servlet.multipart.max-request-size=50MB

⚠️ 大文件上传的第一原则是「别把整个文件读进内存」——否则大文件(几百 MB、GB)会直接把内存撑爆(OOM)MultipartFilegetBytes() 会把整个文件读成 byte[](内存里),大文件绝对不能用;要用 file.transferTo(dest)(直接保存到磁盘,框架内部流式处理)或 file.getInputStream()(流式读,边读边写到目标,内存里只有缓冲区那点数据)。对于超大文件,还要考虑分片上传(chunked upload):前端把大文件切成很多小块(如每块 5MB),分别上传,服务端接收后按顺序合并——好处是①内存友好(每次只处理一小块)、②支持断点续传(某块失败只重传那块、或续传)、③可以并发上传多块加速、④能显示进度。另外要限制上传大小max-file-size),否则恶意的超大文件会打垮服务。还要注意临时文件——上传的文件在解析时可能先存到临时目录,要及时清理(Spring 处理完自动清)。所以大文件上传 = 流式(不读内存)+ 分片(切块合并、断点续传)+ 限大小。

完整版教学

一、为什么要 multipart/form-data

先理解为什么文件上传要用 multipart/form-data

普通表单提交(application/x-www-form-urlencoded):
  把表单字段编码成键值对:username=Tom&age=18
  → 只能传文本(键值对、URL 编码)
  → 不能传二进制文件(文件是二进制、有各种字节,没法编码成键值对)

文件上传的需求:
  要传"文件(二进制)+ 其他字段"
  → 普通表单编码搞不定文件

multipart/form-data 的方案:
  把请求体分成多个"部分(part)":
    每个字段一个 part(普通字段、文件字段)
    每个 part 有自己的头(字段名、文件名、类型)和内容
    用"分隔符(boundary)"隔开各 part
  → 能同时传文件(二进制内容)和普通字段
  → 二进制原样传(不用编码)

为什么叫 multipart(多部分):
  请求体由多个 part 组成(每个字段/文件一个 part)
  → "多部分"的表单数据

所以文件上传用 multipart/form-data(多部分、能传二进制文件)

文件上传要用 multipart/form-data——普通表单(application/x-www-form-urlencoded)把字段编码成键值对,只能传文本、不能传二进制文件。文件上传要传「文件(二进制)+其他字段」,普通表单搞不定。multipart/form-data 的方案:把请求体分成多个「部分(part)」(每个字段一个 part,每个 part 有头(字段名/文件名/类型)和内容,用分隔符 boundary 隔开),能同时传文件(二进制原样)和普通字段。理解「文件上传用 multipart/form-data:普通表单只能传文本(键值对编码)不能传二进制文件;multipart 把请求体分多个 part(每个字段一个,有头和内容,boundary 分隔),能传二进制文件+普通字段」,就理解了为什么要 multipart/form-data。

二、multipart/form-data 的结构

理解 multipart/form-data 请求体的具体结构:

请求头:
  Content-Type: multipart/form-data; boundary=----Boundary123
  → 声明是 multipart、boundary 是分隔符(随机生成、唯一)

请求体(多个 part,用 boundary 分隔):
  ------Boundary123                              ← boundary 开始一个 part
  Content-Disposition: form-data; name="username"  ← part 头(普通字段)

  Tom                                            ← part 内容
  ------Boundary123                              ← 下一个 part
  Content-Disposition: form-data; name="file"; filename="a.jpg"  ← 文件 part
  Content-Type: image/jpeg                       ← 文件类型

  <文件的二进制内容>                              ← 文件内容(二进制)
  ------Boundary123--                            ← 结束(boundary + --)

关键元素:
  boundary:分隔符——隔开各 part(每个 part 前有 --boundary)
    → 服务端靠 boundary 切分各 part
  Content-Disposition:part 的头
    name:字段名
    filename:文件名(有 filename 的是文件 part)
  Content-Type:part 的内容类型(文件的类型)

服务端解析:
  ① 从 Content-Type 拿到 boundary
  ② 用 boundary 切分请求体成多个 part
  ③ 每个 part:解析头(name、filename)+ 内容
  → 取出普通字段和文件

所以 multipart/form-data = boundary 分隔的多个 part(字段+文件)

multipart/form-data 请求体结构:请求头 Content-Type: multipart/form-data; boundary=xxx(声明 multipart + 分隔符 boundary)请求体是多个 part 用 boundary 分隔(每个 part 前有 --boundary,part 有头 Content-Dispositionname 字段名、filename 文件名——有 filename 的是文件 part)和内容,最后 --boundary-- 结束)。服务端解析:从 Content-Type 拿 boundary → 用它切分请求体成多个 part → 每个 part 解析头和内容 → 取出字段和文件。理解「multipart 结构:请求头 Content-Type 声明 boundary;请求体多个 part 用 boundary 分隔(part 有 Content-Disposition 头 name/filename+内容);服务端用 boundary 切分 part 解析取出字段和文件」,就掌握了 multipart 的结构。

三、Servlet 处理文件上传

Servlet 3.0+ 内置支持文件上传:

Servlet 3.0+ 的文件上传支持:
  ① @MultipartConfig 注解(在 Servlet 上):
     @MultipartConfig(maxFileSize = 10*1024*1024, ...)
     → 声明这个 Servlet 支持 multipart、配置大小限制等
  ② request.getParts() / getPart(name):
     Collection<Part> parts = request.getParts();  // 所有 part
     Part filePart = request.getPart("file");       // 某个 part(文件)
     → Part 是每个部分(字段/文件)的抽象

  处理文件 part:
    Part filePart = request.getPart("file");
    String filename = filePart.getSubmittedFileName();  // 文件名
    InputStream in = filePart.getInputStream();          // 文件流
    // 保存文件(流式)
    filePart.write("/data/" + filename);  // 或用流保存

Servlet 3.0 之前(麻烦):
  用 Apache Commons FileUpload 库自己解析 multipart
  → 手动切 part、解析(繁琐)

所以 Servlet 3.0+ 用 @MultipartConfig + getParts 处理文件上传
  比早期用 Commons FileUpload 简单

Servlet 3.0+ 内置文件上传支持:@MultipartConfig 注解(在 Servlet 上,声明支持 multipart、配大小限制)、② request.getParts()/getPart(name)(取所有 part 或某个 part,Part 是每个部分的抽象)。处理文件 part:getPart("file")getSubmittedFileName() 文件名、getInputStream() 文件流、write(path) 保存。Servlet 3.0 之前用 Apache Commons FileUpload 手动解析(繁琐)。理解「Servlet 3.0+文件上传:@MultipartConfig 声明支持+配大小、request.getParts()/getPart 取 part(Part 是每部分抽象);处理文件 part:getSubmittedFileName/getInputStream/write;3.0 前用 Commons FileUpload 手动解析」,就掌握了 Servlet 处理文件上传。

四、Spring MVC 的 MultipartFile

Spring MVC 用 MultipartFile 让文件上传更简单:

Spring MVC 的 MultipartFile:
  用 @RequestParam("file") MultipartFile file 直接接收上传的文件
  → Spring 帮你解析 multipart、封装成 MultipartFile

MultipartFile 的常用方法:
  getOriginalFilename():原始文件名
  getSize():文件大小
  getContentType():文件类型
  getInputStream():文件的输入流(流式读,推荐)
  getBytes():文件字节数组(★ 读进内存,大文件别用)
  transferTo(File dest):保存到磁盘(流式,推荐)
  isEmpty():是否为空

用法:
  @PostMapping("/upload")
  public String upload(@RequestParam("file") MultipartFile file) {
    if (file.isEmpty()) return "空";
    file.transferTo(new File("/data/" + file.getOriginalFilename()));  // 保存
    return "ok";
  }

多文件:
  @RequestParam("files") MultipartFile[] files
  或 List<MultipartFile>

配置(Spring Boot):
  spring.servlet.multipart.max-file-size=10MB    // 单文件大小
  spring.servlet.multipart.max-request-size=50MB // 整个请求大小
  spring.servlet.multipart.enabled=true          // 默认开启

底层:
  Spring 用 MultipartResolver 解析 multipart
  (StandardServletMultipartResolver 基于 Servlet 3.0)

所以 Spring MVC 用 MultipartFile 接收文件(transferTo 保存,别用 getBytes)

Spring MVC 用 MultipartFile 简化文件上传——@RequestParam("file") MultipartFile file 直接接收(Spring 帮你解析 multipart)。常用方法:getOriginalFilename/getSize/getContentType/getInputStream(流式读,推荐)/getBytes(读进内存,大文件别用)/transferTo(dest)(保存到磁盘,流式推荐)。多文件用 MultipartFile[]。配置:spring.servlet.multipart.max-file-size/max-request-size。底层 MultipartResolver 解析。理解「Spring MVC MultipartFile:@RequestParam MultipartFile file 直接接收;方法 getOriginalFilename/getInputStream(流式推荐)/transferTo(保存推荐)/getBytes(读内存大文件别用);多文件 MultipartFile[];配置 max-file-size」,就掌握了 MultipartFile。

五、大文件上传

大文件上传要特殊处理——流式、分片、限大小:

大文件上传的三个关键:

① 流式处理(别读进内存):
   ✗ file.getBytes():把整个文件读成 byte[](内存)→ 大文件 OOM
   ✓ file.transferTo(dest):框架流式保存到磁盘(不占内存)
   ✓ file.getInputStream() + 流式写:边读边写(内存只有缓冲区)
   → 大文件绝对不能读进内存

② 分片上传(chunked upload):
   前端把大文件切成很多小块(如每块 5MB),分别上传
   服务端接收每块 → 按顺序合并成完整文件
   好处:
     - 内存友好(每次只处理一小块)
     - 断点续传(某块失败只重传那块、或从断点续)
     - 并发上传多块加速
     - 显示进度
   流程:
     1. 前端切片(file.slice(start, end))
     2. 每块带(文件唯一标识 + 块序号)上传
     3. 服务端存每块(临时)
     4. 所有块传完 → 服务端按序号合并
   (断点续传:记录已上传的块,续传缺的块)

③ 限制大小(防超大文件打垮):
   max-file-size / max-request-size
   → 超过限制拒绝(防止恶意超大文件耗尽资源)

④ 临时文件清理:
   上传文件解析时可能先存临时目录
   → 处理完及时清理(Spring 自动清)

所以大文件上传 = 流式(不读内存) + 分片(切块合并/断点续传) + 限大小

大文件上传三个关键:① 流式处理(别读进内存)——getBytes() 读整个文件到内存大文件 OOM,用 transferTo(dest)(框架流式保存)或 getInputStream() 流式写(内存只有缓冲区);② 分片上传(chunked upload)——前端切成小块分别上传、服务端按序合并(好处:内存友好、断点续传、并发加速、显示进度;流程:前端切片→每块带文件标识+序号上传→服务端存每块→传完合并);③ 限制大小max-file-size/max-request-size 防超大文件打垮);④ 临时文件清理。理解「大文件上传:①流式(别用 getBytes 读内存 OOM,用 transferTo/getInputStream)②分片上传(前端切小块分别传、服务端按序合并,内存友好+断点续传+并发+进度)③限大小(max-file-size 防打垮)④临时文件清理」,就掌握了大文件上传。

六、实践与总结

总结文件上传的实践:

实践建议:
  ① 普通文件上传 → Spring MVC 的 MultipartFile(简洁)
     transferTo 保存(别用 getBytes,流式)
  ② 大文件 → 流式 + 分片上传(切块合并、断点续传)
  ③ 限制大小(max-file-size / max-request-size)
  ④ 校验(文件类型、大小、内容——防恶意文件)
  ⑤ 存储(本地磁盘 / 对象存储 OSS / 分布式文件系统)

安全注意:
  ① 校验文件类型(别只信扩展名、检查内容/魔数)
  ② 限制大小(防超大文件)
  ③ 文件名处理(防路径穿越 ../、防覆盖)
  ④ 存储隔离(上传的文件别能被当脚本执行)
  ⑤ 病毒扫描(敏感场景)

现代做法:
  ① 大文件/生产 → 直传对象存储(前端拿签名直传 OSS,不经服务端)
     → 减轻服务端压力、更快
  ② 分片上传 → 大文件断点续传(很多网盘、视频上传用)

核心总结:
  文件上传用 multipart/form-data(boundary 分隔 part,传文件+字段)
  Spring 用 MultipartFile 接收(transferTo 保存,别用 getBytes)
  大文件:流式(不读内存)+ 分片(切块合并/断点续传)+ 限大小
  安全:校验类型/大小、防路径穿越、存储隔离

文件上传实践:普通文件用 MultipartFile(transferTo 保存别用 getBytes)、大文件用流式+分片、限大小、校验(类型/大小/内容)、存储(本地/OSS/分布式)。安全:校验文件类型(检查内容不只扩展名)、限大小、文件名处理(防路径穿越../)、存储隔离(别被当脚本执行)、病毒扫描。现代做法:大文件直传对象存储(前端签名直传 OSS 不经服务端)、分片上传断点续传。理解「实践:普通用 MultipartFile(transferTo)/大文件流式+分片/限大小/校验/存储;安全:校验类型(查内容)/限大小/防路径穿越/存储隔离;现代:直传 OSS/分片断点续传」,就掌握了文件上传的实践。

记忆钩子:「文件上传用 multipart/form-data(普通表单 application/x-www-form-urlencoded 只能传文本不能传二进制文件);multipart 把请求体分多个 part(每个字段/文件一个,有 Content-Disposition 头 name/filename+内容)用 boundary 分隔,能传二进制文件+字段;服务端用 boundary 切分 part 解析;Servlet 3.0+用 @MultipartConfig+request.getParts()/getPart;★Spring MVC 用 MultipartFile(@RequestParam MultipartFile file),transferTo(dest)保存/getInputStream 流式(别用 getBytes 读内存大文件 OOM);★大文件上传:①流式(别读内存)②分片上传(前端切小块分别传、服务端按序合并,内存友好+断点续传+并发+进度)③限大小(max-file-size 防打垮);安全:校验类型(查内容不只扩展名)/防路径穿越../;现代直传 OSS」

七、常见误区与追问

  • 误区:普通表单能上传文件。 普通表单用 application/x-www-form-urlencoded 编码,只能传文本(键值对),不能传二进制文件;文件上传要用 multipart/form-data 编码(enctype=“multipart/form-data”),它把请求体分成多个 part、能传二进制文件和普通字段。
  • 误区:用 MultipartFile.getBytes() 读取上传的文件。 大文件绝对不能用 getBytes()——它把整个文件读成 byte[] 放内存,大文件(几百 MB、GB)会 OOM;应该用 transferTo(dest)(框架流式保存到磁盘)或 getInputStream()(流式读、边读边写),内存里只有缓冲区那点数据。
  • 误区:大文件直接一次性上传。 大文件应该分片上传——前端把文件切成小块(如每块 5MB)分别上传、服务端按顺序合并;好处是内存友好(每次处理一小块)、支持断点续传(失败只重传那块)、可并发加速、能显示进度;一次性上传大文件容易超时、失败要全部重来、占内存。
  • 误区:只校验文件扩展名就安全了。 不够——扩展名可以伪造(把 .exe 改成 .jpg);要检查文件内容/魔数(文件头的特征字节)判断真实类型;还要限制大小、处理文件名(防路径穿越 ../)、存储隔离(上传目录别能执行脚本);文件上传是常见的攻击点。
  • 追问:文件上传为什么要用 multipart/form-data? 因为普通表单编码(application/x-www-form-urlencoded)只能把字段编码成键值对文本,无法传输二进制文件(文件是二进制、有各种字节,没法编码成键值对);multipart/form-data 把请求体分成多个「部分(part)」,每个字段/文件一个 part,每个 part 有自己的头(字段名、文件名、类型)和内容,用分隔符(boundary)隔开各 part,这样能同时传输二进制文件(原样传)和普通字段。
  • 追问:大文件上传怎么处理才不会 OOM? 关键是「别把整个文件读进内存」:① 用 MultipartFile.transferTo(dest) 直接保存到磁盘(框架内部流式处理,不占内存)或 getInputStream() 流式读(边读边写到目标,内存只有缓冲区);② 对于超大文件用分片上传(前端切成小块分别上传、服务端按序合并,每次只处理一小块,内存友好);③ 限制上传大小(max-file-size)防止恶意超大文件;④ 避免用 getBytes()(会把整个文件读成 byte[] 放内存)。
  • 追问:分片上传是怎么实现断点续传的? 分片上传把大文件切成很多小块(每块有文件的唯一标识 + 块序号),分别上传;服务端记录每个文件已经上传成功的块;如果上传中断,重新开始时,前端(或服务端)先查询已上传的块,只上传还没传成功的块(断点续传),而不用从头重传整个文件;所有块传完后,服务端按块序号合并成完整文件;这样既支持断点续传、又内存友好、还能并发上传多块加速。

八、加强记忆

文件上传的核心是「HTTP 用 multipart/form-data 编码把文件和表单字段打包在请求体里」——普通表单(application/x-www-form-urlencoded)只能传文本、不能传二进制文件;multipart/form-data 把请求体分成多个「部分(part)」(每个字段/文件一个 part,有头 Content-Dispositionname 字段名、filename 文件名)和内容,用分隔符 boundary 隔开),能同时传文件(二进制)和字段。服务端用 boundary 切分 part 解析Servlet 3.0+ 用 @MultipartConfig + request.getParts()/getPartSpring MVC 用 MultipartFile@RequestParam("file") MultipartFile filetransferTo(dest) 保存 / getInputStream() 流式,别用 getBytes() 读进内存)。大文件上传要注意① 流式处理(别读进内存,否则 OOM)、② 分片上传(前端切小块分别上传、服务端按序合并,内存友好 + 断点续传 + 并发加速 + 显示进度)、③ 限制大小(max-file-size 防打垮)、④ 临时文件清理安全:校验文件类型(检查内容/魔数不只扩展名)、限大小、防路径穿越(../)、存储隔离;现代大文件常直传对象存储(OSS)。一句话「文件上传用 multipart/form-data(boundary 分隔多个 part,传文件+字段,普通表单只能传文本);Spring 用 MultipartFile(transferTo 保存/getInputStream 流式,别用 getBytes 读内存 OOM);大文件:流式(不读内存)+分片(切块合并/断点续传)+限大小;安全校验类型查内容/防路径穿越;现代直传 OSS」。