文件上传下载(场景):
所谓文件上传下载就是将本地文件上传到服务器端,从服务器端下载文件到本地的过程。
例如目前网站需要上传头像、上传下载图片或网盘等功能都是利用文件上传下载功能实现的。
文件上传下载实际上是两步操作,
第一是文件上传,就是将本地文件上传到服务器端,实现文件多用户之间的共享,
第二是文件下载,就是将服务器端的文件下载到本地磁盘。
文件上传下载实现原理
文件上传实现流程如下:
Ø 客户端浏览器通过文件浏览框,选择需要上传的文件内容(其中包括文件路径及文件内容)。
Ø 客户端浏览器通过点击上传按钮,将本地文件上传到服务器端。
Ø 服务器端通过程序接收本地文件内容,并将其保存在服务器端磁盘中。
* 文件上传
* 客户端
* 文件上传页面(form)* 请求方式一定是POST.
* 文件上传域(<input type='file'>)必须具有name属性.
* 表单的enctype属性值设置为"multipart/form-data".
upload.jsp:
<body>
<form action="/19_upload/upload" method="post" enctype="multipart/form-data">
文件描述:<input type="text" name="filetext"><br>
<input type="file" name="upload"><br>
<input type="submit" value="上传">
</form>
</body>
* 扩展:浏览器内核产品不同(不建议使用IE)
* IE浏览器:IE6.0\7.0 IE8.0\9.0 IE10\11
* 其他浏览器:Webkit(苹果)
* 苹果浏览器
* 火狐浏览器:自主内核产品.
* 谷歌浏览器:自主内核产品.
* 众多国内浏览器:
* 百度浏览器:号称自主内核.
* 腾讯浏览器:号称自主内核.
* 遨游浏览器:号称自主内核.
* 360\搜狗\猎豹等...
* 服务器端
* 导入工具包:commons-fileupload.jar和commons-io.jar包.
commons-fileupload.组件工作流程:
* 实例化DiskFileItemFactory工厂类.
* 实例化ServletFileUpload类.
ServletFileUpload upload = new ServletFileUpload(factory);
* 利用upload的parseRequest(request)方法从Request对象中获取文件上传的内容(List集合).
* 遍历获取到的List集合.
* 如果是普通项 - 获取普通项的文本内容.
* isFormField():判断当前是否是普通项,true表示是.
* getFieldName():获取普通项的name属性值.
* getString():获取普通项的文本内容.
* 如果是文件项
* getName():获取上传文件的名称.
* getInputStream():获取上传文件的输入流.
* 通过保存的路径,创建文件的输出流.
* 利用IOUtils.copy(inputStream,OutputStream)方法将上传文件进行保存.
public class UploadServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request, response);
}
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 创建DiskFileItemFactory文件项工厂对象
DiskFileItemFactory factory = new DiskFileItemFactory();
// 通过工厂对象获取文件上传请求核心解析类ServletFileUpload
ServletFileUpload upload = new ServletFileUpload(factory);
try {
// 使用ServletFileUpload对应Request对象进行解析
List<FileItem> items = upload.parseRequest(request);
// 遍历每个fileItem
for (FileItem fileItem : items) {
// 判断fileItem是否是上传项
if (fileItem.isFormField()) { 通过FileItem.isFormField()方法判断当前是普通项还是文件项 返回布尔值:true:普通项;false:文件项
// 返回true:表示不是上传项
String fieldName = fileItem.getFieldName(); //获取普通项的name属性值
String str = fileItem.getString("utf-8"); //获取普通项的文本内容
System.out.println(fieldName+" : "+str);
}else{
// 返回false:表示是上传项
String name = fileItem.getName(); //获取文件项的上传文件名称
InputStream in = fileItem.getInputStream(); //获取文件项的上传文件输入流
String uploadPath = getServletContext().getRealPath("/upload");
OutputStream out = new FileOutputStream(new File(uploadPath, name));
int b;
while ((b = in.read()) != -1) {
out.write(b);
}
out.close();
in.close();
}
}
} catch (FileUploadException e) {
e.printStackTrace();
}
}
}
动态多文件上传:
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>My JSP 'upload.jsp' starting page</title>
</head>
<script type="text/javascript">
function add(){
// 在div中添加上传输入项
document.getElementById("uploaddiv").innerHTML += "<div><input type='file' name='upload' /><input type='button' value='删除' onclick='del(this);' /></div>";
}
function del(obj){
// 传入obj 就是你点击 按钮对象
var div = obj.parentNode;
div.parentNode.removeChild(div);
}
</script>
<body>
<form action="/19_upload/upload" method="post" enctype="multipart/form-data">
<input type="button" value="上传文件" onclick="add();"/>
<div id="uploaddiv"></div>
<input type="submit" value="开始上传" />
</form>
</body>
</html>
* 将文件上传至WEB-INF目录下:
* WEN-INF目录外与WEB-INF目录中的区别:
* WEN-INF目录外:在浏览器中可以访问的(安全低).
* WEB-INF目录中:在浏览器中不能访问的.
获取文件上传路径的代码,应该修改为如下内容:
String uploadPath =getServletContext().getRealPath("/WEB-INF/upload");
* 上传文件名称的处理:
* 可能使用的浏览器版本过旧,获取到的文件上传的名称,而是选择上传文件的完整路径.
C:\Users\JYL\Desktop\day0106\笔记\readme.txt
* 解决办法:
int index = fileName.lastIndexOf("\\");
if(index >= 0){
fileName.substring(index+1);
}
* 上传文件的中文乱码问题:
* 普通项的中文乱码:
* 利用FileItem的getString(encoding)方法来解决.fileItem.getString("utf-8");
* 文件项的中文乱码:
* POST方式的请求参数:request.setCharacterEncoding("utf-8")
* 上传文件同名的问题:
* 问题:
默认情况下,上传多次同名的文件时,新的文件会覆盖旧的文件.
* 解决:
将每个上传的文件名,提供一个唯一的标识(拼在文件名中).
//为每个上传的文件名称,增加一个随机名称值
fileName = UUID.randomUUID().toString()+fileName;* 注意:如果开发真实案例时,需要保存真实文件名称(给用户看的)和上传后处理的文件(真正做下载功能用的)名称.
* 一个目录不能存放过多文件:
* 问题:* 长时间使用后,上传目录的体积多大.
* 上传目录保存的文件过多,查找文件过慢.
* 解决:分级存储
* 按照用户名称分级存储.
* 按照日期时间分级存储.
* 指定一个上传子目录最大存储空间.
* 按照hashcode分级存储.
可以将以上代码封装成一个工具类
* 处理文件上传大小的限制:
* 单个文件大小的限制:3M
* 利用ServletFileUpload的setFileSizeMax(字节数).* 问题:如果上传的单个文件大小,大于限制的大小时:抛异常.
FileSizeLimitExceededException
在实现多文件上传时,还需要设置上传文件的总大小。
利用ServletFileUpload的setSizeMax(long)方法进行设置,其中参数表示设置的大小,单位为字节数,
例如servletFileUpload.setSizeMax(1024*10)表示上限为10KB。
一旦上传的文件大小超过限制大小时,会抛出FileUploadBase.SizeLimitExceededException异常,可以捕获该异常后向页面输出相应的错误信息。具体实现代码如下:
public class UploadServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request, response);
}
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
DiskFileItemFactory factory = new DiskFileItemFactory();
ServletFileUpload upload = new ServletFileUpload(factory);
// 设置单个上传文件的大小
upload.setFileSizeMax(1024 * 10);
try {
List<FileItem> items = upload.parseRequest(request);
for (FileItem fileItem : items) {
if (fileItem.isFormField()) {
……
}else{
String name = fileItem.getName();
……
in.close();
}
}
} catch (FileUploadException e) {
if (e instanceof FileUploadBase.FileSizeLimitExceededException) {
// 在request中保存错误信息
request.setAttribute("msg", "上传失败!上传的文件超出了10KB!");
// 转发到index.jsp页面中!在index.jsp页面中需要使用${msg}来显示错误信息
request.getRequestDispatcher("/index.jsp").forward(request, response);
}
e.printStackTrace();
}
}
}
* 上传文件总大小的限制:10M
* upload.setSizeMax(1024*1024*10);* 抛异常:SizeLimitExceededException
* 默认情况下:上传文件的输入流存储在服务器端的内存中(缓存).
* 问题:
* 当上传的单个文件过大时,导致服务器端的内存空间不足(性能下降)
* 解决:
* 指定上传文件的缓存大小.
* 如果上传文件的缓存大于指定的缓存(内存中)大小,使用临时文件(硬盘中)的方式.
* 如何实现:
* 指定上传的缓存大小:
* factory.setSizeThreshold(1024*1024);
* factory.setRepository(new File(getServletContext().getRealPath("/tmp")));
* 机制:当文件上传成功后,删除临时目录中的临时文件.
fileItem.delete();
public class UploadServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request, response);
}
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
DiskFileItemFactory factory = new DiskFileItemFactory();
ServletFileUpload upload = new ServletFileUpload(factory);
// 设置单个上传文件的大小
upload.setSizeMax(1024 * 10);
try {
List<FileItem> items = upload.parseRequest(request);
for (FileItem fileItem : items) {
if (fileItem.isFormField()) {
……
}else{
String name = fileItem.getName();
……
in.close();
}
}
} catch (FileUploadException e) {
if (e instanceof FileUploadBase.SizeLimitExceededException) {
// 在request中保存错误信息
request.setAttribute("msg", "上传失败!上传的文件超出了10KB!");
// 转发到index.jsp页面中!在index.jsp页面中需要使用${msg}来显示错误信息
request.getRequestDispatcher("/index.jsp").forward(request, response);
}
e.printStackTrace();
}
}
}
* 上传文件比较大时,上传的速度变慢?
* 原因就是输入流与输出流的问题.
* 输出流:new BufferedOutputStream(new FileOutputStream(new File(realPath,fileName)));
* 输入流:new BufferedInputStream(fileItem.getInputStream());
* 文件上传的进度条的功能:
* 服务器端:监听文件上传的整个过程(从开始上传到上传结束)* 需要文件上传的监听器.
upload.setProgressListener(new ProgressListener() {
/**
* update(long pBytesRead, long pContentLength, int pItems)
* * 参数pBytesRead:到目前为止,已经读取上传文件的大小.
* * 参数pContentLength:上传文件的总大小.
* * 参数pItems:当前上传文件,是表单的第几个元素.
* 计算以下四个结果:
* * 已用时间:当前时间 - 开始时间
* * 上传速度:已经上传大小 / 已用时间
* * 剩余大小:总大小 – 已经上传大小
* * 剩余时间:剩余大小 / 速度
*/
public void update(long pBytesRead, long pContentLength, int pItems) {
// 获取文件上传的当前时间
long currentTime = System.currentTimeMillis();
// 已用时间:当前时间 - 开始时间
long useTime = currentTime - startTime;
// 上传速度:已经上传大小 / 已用时间
long speed = pBytesRead / useTime;
// 剩余大小:总大小 – 已经上传大小
long restBytes = pContentLength - pBytesRead;
// 剩余时间:剩余大小 / 速度
long restTime = restBytes / speed;
System.out.println("上传速度为: "+speed+" , "+"当前还有 "+restBytes+" 没有上传完成, "+"还有 "+restTime+" 上传完成");
}
});
* 客户端:添加进度条的内容(异步交互技术Ajax)
* 文件下载
文件下载实现流程如下:
Ø 客户端浏览器通过点击下载按钮,将服务器端保存的文件下载到本地磁盘。
Ø 服务器端通过程序将服务器端文件响应给客户端。
* 客户端
* 显示文件下载列表(一):
<h4><a href="${pageContext.request.contextPath }/downs/1.txt">1.txt</a></h4>
<h4><a href="${pageContext.request.contextPath }/downs/2.xls">2.xls</a></h4>
<h4><a href="${pageContext.request.contextPath }/downs/3.zip">3.zip</a></h4>
<h4><a href="${pageContext.request.contextPath }/downs/4.jpg">4.jpg</a></h4>
* 问题:
* 如果浏览器本身支持下载文件的格式,并不提供下载,而是直接显示.
* 如果浏览器本身不支持下载文件格式,提供下载功能.
* 显示文件下载列表(二):
<h4><a href="${pageContext.request.contextPath }/down?filename=1.txt">1.txt</a></h4>
<h4><a href="${pageContext.request.contextPath }/downs?filename=2.xls">2.xls</a></h4>
<h4><a href="${pageContext.request.contextPath }/downs?filename=3.zip">3.zip</a></h4>
<h4><a href="${pageContext.request.contextPath }/downs?filename=4.jpg">4.jpg</a></h4>
* 服务器端
* 客户端:
* 文件下载列表页面:
* a标签的href属性,直接指定对应服务器端文件的路径.
* 如果浏览器本身支持这种文件格式,直接打开.
* a标签的href属性,指定一个服务器端的程序(Servlet)的路径.
* 服务器端:
* 服务器端接收客户端请求下载(包含下载的文件名称).
* 查询数据库表(文件名称,文件存储路径).
* 通过文件存储路径,找到对应要下载的文件(文件输入流).
* 创建输出流(response.getOutputStream()方法).
* 调用IOUtils.copy(inputStream,outputStream).
* 注意:要想服务器端下载而不是显示必加:
response.setContentType(getServletContext().getMimeType(filename));
response.setHeader("Content-Disposition", "attachment;filename="+filename);
* 解决下载中的中文乱码问题:
* 解决请求参数中文乱码:
filename = new String(filename.getBytes("ISO-8859-1"),"utf-8");
* 解决下载文件名称中文乱码:
String userAgent = request.getHeader("User-Agent");
if(userAgent.contains("MSIE")){
/ IE浏览器
filename = URLEncoder.encode(filename, "utf-8");
filename = filename.replace("+", " ");
}else{
// 其他浏览器
BASE64Encoder base64Encoder = new BASE64Encoder();
filename = "=?utf-8?B?" + base64Encoder.encode(filename.getBytes("utf-8")) + "?=";
}