文件的上传与下载
文件上传
1.1前台页面
在做文件上传时,会有一个上传文件的页面,首先我们需要一个表单,并且表单的请请求方式为Post;
其次我们的from表单的enctype要设为"multipart/form-data" ,即 enctype=“multipart/form-data”
(意思是设置表单的类型为文件上传表单. )
默认下表单的类型为 “application/x-www-form-urlencoded” (不能用于文件上传)
前端表单配置
示例代码:
<!-- 文件上传表单
1.表单提交类型为 method="post"
2.表单类型 enctype="multipart/form-data"
3.表单元素类型 文件域设置name 属性值
-->
<form action="/upload" method="post" enctype="multipart/form-data">
姓名<input type="text" name="name"><br>
文件:<input type="file" name="file"><br>
<input type="submit" value="上传">
</form>
/**
* 使用注释@MultipartConfig 将一个Servlet标识为支持文件上传
* Servlet 将multipart / form-data 的Post请求封装为Part,通过Part对上传的文件进行操作
*
*/@WebServlet("/uploadServlet")
@MultipartConfig
public class UploadServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("文件上传");
// 获得请求的编码格式
req.setCharacterEncoding("UTF-8");
// 获取普通类单项(获取参数
String uname = req.getParameter("uname");
System.out.println("uname"+uname);
// 获取Part对象 (Servlet 将 multipart/form-data 的 POST 请求封装为Part对象
Part part = req.getPart("file");
// 通过Part对象得到上传的文件名
String fileName = part.getSubmittedFileName();
System.out.println("上传的文件名"+fileName);
// 得到文件存放的路径
String filePath = req.getServletContext().getRealPath("/");
System.out.println("文件存放的路径"+filePath);
// 上传文件到指定目录
part.write(filePath+"/"+fileName) ;
}
}
文件下载
超链接下载
当我们在HTML或jsp页面使用a标签是, 愿意是希望能够进行跳转,当但超链接遇到浏览器不能识别的资源时会自动下载,当遇到浏览器能够直接显示的资源时,浏览器会默认的显现出来,比如 txt png jpg
当然也可以通过download属性规定浏览器下载.但有些浏览器并不支持
默认下载
当但超链接遇到浏览器不能识别的资源时会自动下载,当遇到浏览器能够直接显示的资源时,浏览器会默认的显现出来
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>文件下载</title>
</head>
<body>
<!--浏览器能识别的资源-->
<a href="">文本文件</a>
<a href="down/2135.jpg_wh300.jpg">图片文件</a>
<!-- 浏览器不能识别的资源-->
<a href="">压缩文件</a>
</body>
</html>
指定download属性下载
<!--浏览器能识别的资源-->
<a href="xxx.txt">文本文件</a>
<a href="down/2135.jpg_wh300.jpg">图片文件</a>
<!-- 浏览器不能识别的资源-->
<a href="xxx.zip">压缩文件</a>
<hr>
<a href="">文本文件</a>
<a href="down/2135.jpg_wh300.jpg" download="图片">图片文件</a>
download属性可以不写任何信息,会使用默认文件名.如果设置了download属性的值,则使用设置的值为文件名.当用户打开浏览器点击链接时会直接下载
后台下载
实现步骤
设置响应头
- 需要通过resp.setContentType 方法设置 Content-type 头字段的值 , 为浏览器无法使用某种方法或激活某个程序来处理的MIME类型,例如"application/octet-stream"或"application/x-msdownload"等
- 需要通过 resp.setHeader方法设置Content-Dispostion头的值为"attadchment;filename=文件名"(Content-Disposition:指定 attachment 模式,强制浏览器触发下载行为)
读取文件并写入响应流 - 读取下载文件(通过文件输入流(FileInputStream)读取本地文件)
- 调用 响应输出流(resp.getOutputStream方法) 向客户端写传输文件内容
// 1. 设置请求编码
//设置请求体的编码为 UTF-8,确保能正确解析中文文件名等参数。
request.setCharacterEncoding("UTF-8");
// 2. 获取文件路径
//获取 Web 应用根目录的物理路径
String path = getServletContext().getRealPath("/");
// 3. 获取文件名参数
String name = request.getParameter("filename"); // 原图错误:Tjlename
// 4. 验证文件名合法性(新增:防止路径穿越攻击)
if (name == null || name.contains("..") || name.contains("/")) {
response.sendError(400, "Invalid filename");
return;
}
// 5. 创建 File 对象
File file = new File(path + name);
//判断文件是否存在并且是一个标准文件
if(!file.exists()&&!file.isFile()){
//设置响应头
resp.setContentType("application/octet-stream");
//设置响应类型
resp.setHeader("Content-Disposition", "attachment; filename="+fileName);
//流操作
//得到file文件的输入流
InputStream in = new FileInputStream(file);
//得到字节输出流
ServletOutputStream out = resp.getOutputStream();
//定义byte数组
byte[] bytes = new byte[1024];
//定义长度
int len=0;
//循环输出
while((len=in.read(bytes))!=-1){
//输出
out.write(bytes,0,len);
}
//关闭资源
out.close();
in.close();
}else{
resp.getWriter().write("文件不存在!,请重试");
resp.getWriter().close();
}
}
<form action="down">
文件名:<input type="text" name="fileName" placeholder="请输入要下载的文件名">
<button>下载</button>
</form>
响应头:控制浏览器行为
Content-Type:定义响应类型
作用:
告知浏览器响应体的数据类型(MIME 类型)。若设置为 text/html,浏览器会直接渲染 HTML;若设置为 application/octet-stream,浏览器会将其视为二进制流,触发下载行为。
Content-Disposition:定义下载行为
作用:
attachment 模式强制浏览器弹出下载对话框,filename 指定默认文件名。
为何必须设置:
若未设置此头,浏览器可能根据 URL 或内容猜测文件名(如 down?file=123 生成 down 作为文件名)。
获取 example.jar 的几种方法
example.jar 是一个示例的 JAR 文件,您可以通过以下方式获取或生成它:
流式传输
使用 FileInputStream 读取文件
为何必须使用输入流:
文件可能较大(如 GB 级),直接加载到内存会导致 内存溢出(OOM)。流式传输通过分块读写(缓冲区),逐段处理文件,降低内存压力。
通过 ServletOutputStream 写入响应
为何必须使用输出流:
HTTP 响应本质是字节流,必须通过流式传输将文件内容写入响应体。
类比解释
假设你要寄一封信:
信件内容:相当于文件内容(如 data.pdf 的二进制数据)。
信封:相当于 HTTP 响应头(Content-Type、Content-Disposition 等)。
邮局:相当于 ServletOutputStream,负责将信件内容(字节流)装入信封并发送。
如果没有 ServletOutputStream:
相当于你直接告诉收件人“信在某个地址”,对方需要自己去取。这会暴露你的文件路径(如服务器上的 /files/data.pdf),导致安全风险。
为何不能直接操作文件路径?
错误示例
// 错误做法:直接重定向到文件路径
javaresponse.sendRedirect("/files/data.pdf");
后果:
- 暴露服务器路径:客户端会看到服务器文件路径(如 http://yourserver.com/files/data.pdf)。
- 路径遍历攻击:攻击者可构造 URL(如 http://yourserver.com/files/…/…/etc/passwd)窃取敏感文件。
- 无法控制下载行为:浏览器可能直接显示文件(如图片),而非触发下载。
分块传输逻辑详解:
//得到file文件的输入流
InputStream in = new FileInputStream(file);
//得到字节输出流
ServletOutputStream out = resp.getOutputStream();
//定义byte数组
byte[] bytes = new byte[1024];
//定义长度
int len=0;
//循环输出
while((len=in.read(bytes))!=-1){
//输出
out.write(bytes,0,len); // 分块写入响应流
}
逐行解释
1.byte[] bytes= new byte[4096]
- 创建一个 4KB 的缓冲区(内存块),用于临时存储从文件中读取的数据。
- 为什么是 4KB?
平衡内存占用和 IO 效率。缓冲区太小(如 1KB)会增加 IO 次数;太大(如 10MB)会浪费内存
2.in.read( bytes)
从文件输入流(FileInputStream)中读取数据,每次最多读取 4KB,返回实际读取的字节数(bytesRead)。
底层操作:
文件内容 → 读取到内存缓冲区 → 准备写入响应流。
3.out.write(bytes, 0, bytesRead)
将缓冲区中的数据写入响应输出流(ServletOutputStream)。
参数解释:
- buffer:数据来源的字节数组。
- 0:从缓冲区的第 0 个字节开始。
- bytesRead:写入的实际长度(避免缓冲区未填满时的无效数据)
4.循环条件 while (… != -1)
- in.read() 返回 -1 表示文件已读完,循环终止