一、文件上传概述

1、实现web开发中的文件上传功能,需完成如下二步操作:

•在web页面中添加上传输入项
•在servlet中读取上传文件的数据,并保存到本地硬盘中.

2、如何在web页面中添加上传输入项?   

<input type=“file”>标签用于在web页面中添加文件上传输入项,设置文件上传输入项时须注意:
•必须要设置input输入项的name属性,否则浏览器将不会发送上传文件的数据.
•必须把form的enctype属值设为multipart/form-data.设置该值后,浏览器在上传文件时,将把文件数据附带在http请求消息体中,并使用MIME协议对上传的文件进行描述,以方便接收方对上传数据进行解析和处理.

3、如何在Servlet中读取文件上传数据,并保存到本地硬盘中?

•Request对象提供了一个getInputStream方法,通过这个方法可以读取到客户端提交过来的数据.但由于用户可能会同时上传多个文件,在servlet端编程直接读取上传数据,并分别解析出相应的文件数据是一项非常麻烦的工作.例:

<!--upload.jsp  -->
<form action="${pageContext.request.contextPath }/servlet/UploadServlet3" enctype="multipart/form-data" method="post">
上传用户:<input type="text" name="username"><br/>
上传文件1:<input type="file" name="file1"><br/>
上传文件2:<input type="file" name="file2"><br/>
<input type="submit" value="上传">
</form>
public class UploadServlet extends HttpServlet {

    public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException { //如果表单类型为multipart/form-data的话,在servlet中注意就不能采用传统方式获取数据
/*String username = request.getParameter("username");
System.out.println(username);*/ InputStream in = request.getInputStream();
int len = 0;
byte buffer[] = new byte[1024];
while((len=in.read(buffer))>0){
System.out.println(new String(buffer,0,len));//解析麻烦
}
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}

•为方便用户处理文件上传数据,Apache 开源组织提供了一个用来处理表单文件上传的一个开源组件( Commons-fileupload ),该组件性能优异,并且其API使用极其简单,可以让开发人员轻松实现web文件上传功能,因此在web开发中实现文件上传功能,通常使用Commons-fileupload组件实现.使用Commons-fileupload组件实现文件上传,需要导入该组件相应的支撑jar包:Commons-fileupload和commons-io.commons-io 不属于文件上传组件的开发jar文件,但Commons-fileupload 组件从1.1 版本开始,它工作时需要commons-io包的支持.

二、fileupload组件工作流程

1、核心API—DiskFileItemFactory

DiskFileItemFactory 是创建 FileItem 对象的工厂,这个工厂类常用方法:

//设置内存缓冲区的大小,默认值为10K.当上传文件大于缓冲区大小时, fileupload组件将使用临时文件缓存上传文件.
public void setSizeThreshold(int sizeThreshold)
//指定临时文件目录,默认值为System.getProperty("java.io.tmpdir").
public void setRepository(java.io.File repository)
//构造函数
public DiskFileItemFactory(int sizeThreshold, java.io.File repository)

2、核心API—ServletFileUpload

ServletFileUpload 负责处理上传的文件数据,并将表单中每个输入项封装成一个 FileItem 对象中.常用方法有:

//判断上传表单是否为multipart/form-data类型
boolean isMultipartContent(HttpServletRequest request)
//解析request对象,并把表单中的每一个输入项包装成一个fileItem 对象,并返回一个保存了所有FileItem的list集合.
List parseRequest(HttpServletRequest request)
//设置上传文件的最大值
setFileSizeMax(long fileSizeMax)
//设置上传文件总量的最大值
setSizeMax(long sizeMax)
//设置编码格式
setHeaderEncoding(java.lang.String encoding)
//上传进度监听器
setProgressListener(ProgressListener pListener)

三、文件上传案例

原理版:

//处理上传数据
public class UploadServlet2 extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
try{
DiskFileItemFactory factory = new DiskFileItemFactory();
ServletFileUpload upload = new ServletFileUpload(factory); List<FileItem> list = upload.parseRequest(request);
for(FileItem item : list){
if(item.isFormField()){
//为普通输入项
String inputName = item.getFieldName();
String inputValue = item.getString();
System.out.println(inputName + "=" + inputValue);
}else{
//代表当前处理的item里面封装的是上传文件
//C:\Documents and Settings\ThinkPad\桌面\a.txt a.txt
String filename = item.getName().substring(item.getName().lastIndexOf("\\")+1);
InputStream in = item.getInputStream();
int len = 0;
byte buffer[] = new byte[1024];
FileOutputStream out = new FileOutputStream("c:\\" + filename);
while((len=in.read(buffer))>0){
out.write(buffer, 0, len);
}
in.close();
out.close();
}
}
}catch (Exception e) {
throw new RuntimeException(e);//直接抛给页面不太好
}
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}

四、上传文件的处理细节

1、上传文件的中文乱码
1>解决文件的乱码
ServletFileUpload.setHeaderEncoding("UTF-8"),或者request的setCharacterEncoding
2>解决普通输入项的乱码(注意,表单类型为multipart/form-data的时候,设置request的编码是无效的)
FileItem.setString("UTF-8");  //解决乱码

2、在处理表单之前,要记得调用isMultipartContent:
ServletFileUpload.isMultipartContent方法判断提交表单的类型,如果该方法返回true,则按上传方式处理,否则按照传统方式处理表单即可.

3、设置解析器缓冲区的大小,以及临时文件的删除

由于文件大小超出DiskFileItemFactory.setSizeThreshold方法设置的内存缓冲区的大小时,Commons-fileupload组件将使用临时文件保存上传数据,因此在程序结束时,务必调用FileItem.delete方法删除临时文件.Delete方法的调用必须位于流关闭之后,否则会出现文件占用,而导致删除失败的情况.

4、文件存放位置

在做上传系统时,千万要注意上传文件的保存目录,这个上传文件的保存目录绝对不能让外界直接访问到.比如用户上传一个jsp页面,然后可以在里面操作服务器关闭,格式化C盘等.

5、限制上传文件的类型
在处理上传文件时,判断上传文件的后缀名是不是允许的

6、限制上传文件的大小
调用解析器的ServletFileUpload.setFileSizeMax(1024*1024*5);就可以限制上传文件的大小,如果上传文件超出限制,则解析器会抛FileUploadBase.FileSizeLimitExceededException异常,程序员通过是否抓到这个异常,进而就可以给用户友好提示.

7、为避免上传文件的覆盖,程序在保存上传文件时,要为每一个文件生成一个唯一的文件名.
8、为避免在一个文件夹下面保存超过1000个文件,影响文件访问性能,程序应该把上传文件打散后存储.
9、监听上传进度,进度条之类,要结合js.

完善后的上传案例:

public class UploadServlet3 extends HttpServlet {

    public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException { List<String> types = Arrays.asList("jpg", "gif", "avi", "txt"); try {
DiskFileItemFactory factory = new DiskFileItemFactory();
factory.setSizeThreshold(1024 * 1024);
factory.setRepository(new File(this.getServletContext().getRealPath("/temp"))); ServletFileUpload upload = new ServletFileUpload(factory);
// setProgressListener具体用法可参考apache用户指导
upload.setProgressListener(new ProgressListener() {
public void update(long pBytesRead, long pContentLength,int pItems) {
System.out.println("当前已解析:" + pBytesRead);
}
}); upload.setFileSizeMax(1024 * 1024 * 5);
if (!upload.isMultipartContent(request)) {
// 按照传统方式获取表单数据
request.getParameter("username");
return;
}
upload.setHeaderEncoding("UTF-8");
List<FileItem> list = upload.parseRequest(request); for (FileItem item : list) {
if (item.isFormField()) {
// 为普通输入项
String inputName = item.getFieldName();
String inputValue = item.getString("UTF-8");
// inputValue = new String(inputValue.getBytes("iso8859-1"),"UTF-8");
System.out.println(inputName + "=" + inputValue);
} else {
String filename = item.getName().substring(item.getName().lastIndexOf("\\")+1);
if (filename == null || filename.trim().equals("")) {
continue;
}
// 设置上传文件类型
String ext = filename.substring(filename.lastIndexOf(".") + 1);
if (!types.contains(ext)) {
request.setAttribute("message", "本系统不支持" + ext + "这种类型");
request.getRequestDispatcher("/message.jsp").forward(request, response);
return;
}
InputStream in = item.getInputStream();
int len = 0;
byte buffer[] = new byte[1024];
String saveFileName = generateFileName(filename);
String savepath = generateSavePath(this.getServletContext()
.getRealPath("/WEB-INF/upload"), saveFileName);
FileOutputStream out = new FileOutputStream(savepath
+ File.separator + saveFileName);
while ((len = in.read(buffer)) > 0) {
out.write(buffer, 0, len);
}
in.close();
out.close();
item.delete(); // 删除临时文件
}
}
} catch (FileUploadBase.FileSizeLimitExceededException e) {
request.setAttribute("message", "文件大小不能超过5m");
request.getRequestDispatcher("/message.jsp").forward(request,response);
return;
} catch (Exception e) {
throw new RuntimeException(e);
}
request.setAttribute("message", "上传成功!!");
request.getRequestDispatcher("/message.jsp").forward(request, response);
} public String generateSavePath(String path, String filename) {
int hashcode = filename.hashCode();
int dir1 = hashcode & 15;
int dir2 = (hashcode >> 4) & 0xf; String savepath = path + File.separator + dir1 + File.separator + dir2;
File file = new File(savepath);
if (!file.exists()) {
file.mkdirs();
}
return savepath;
} public String generateFileName(String filename) {
// 83434-83u483-934934
return UUID.randomUUID().toString() + "_" + filename;
} public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}

五、文件下载,下载上面例子中上传的文件

//列出网站所有文件
public class ListFileServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException { String path = this.getServletContext().getRealPath("/WEB-INF/upload");
Map map = new HashMap();
listfile(new File(path),map); request.setAttribute("map", map);
request.getRequestDispatcher("/listfile.jsp").forward(request, response);
} //*如何保存递归出来的资源
public void listfile(File file,Map map){
if(!file.isFile()){
File children[] = file.listFiles();
for(File f : children){
listfile(f,map);
}
}else{
String filename = file.getName().substring(file.getName().indexOf("_")+1);
//页面中要这么显示:<a href="/servlet?filename=文件在服务器的名称">文件的原始文件名</a>
map.put(file.getName(),filename);
}
} public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
} <!--listfile.jsp -->
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>文件列表</title>
</head>
<body>
下载文件有:<br/>
<c:forEach var="entry" items="${requestScope.map}">
       <!--c:url标签自动进行url编码 -->
<c:url var="url" value="/servlet/DownLoadServlet">
<c:param name="filename" value="${entry.key}"></c:param>
</c:url>
${entry.value } <a href="${url }">下载</a><br/>
</c:forEach>
</body>
</html> public class DownLoadServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException { //得到要下载的文件名uuid,url编码过来的数据只能手动解码
String filename = request.getParameter("filename");
filename = new String(filename.getBytes("iso8859-1"), "UTF-8"); // 找出这个文件 url,"/WEB-INF/upload"这里没必要用File.separator,c:\\盘地址才使用
String path = this.getServletContext().getRealPath("/WEB-INF/upload")
+ File.separator + getpath(filename); File file = new File(path + File.separator + filename);
if (!file.exists()) {
request.setAttribute("message", "对不起,您要下载的资源已被删除");
request.getRequestDispatcher("/message.jsp").forward(request,response);
return;
} // 得到文件的原始文件名
String oldname = file.getName().substring(file.getName().indexOf("_") + 1); // 通知浏览器以下载方式打开下面发送的数据,servlet中要URLEncoder编码
response.setHeader("content-disposition", "attachment;filename="
+ URLEncoder.encode(oldname, "UTF-8")); FileInputStream in = new FileInputStream(file);
int len = 0;
byte buffer[] = new byte[1024];
OutputStream out = response.getOutputStream();
while ((len = in.read(buffer)) > 0) {
out.write(buffer, 0, len);
}
in.close();
} public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
} public String getpath(String filename) {
int hashcode = filename.hashCode();
int dir1 = hashcode & 15;
int dir2 = (hashcode >> 4) & 0xf;
return dir1 + File.separator + dir2; // 3/5
}
}

ps:文件上传下载经典案例,见文件.

最新文章

  1. 【BZOJ】3993: [SDOI2015]星际战争
  2. Shell中的判断标志
  3. python Django教程 之 模型(数据库)、自定义Field、数据表更改、QuerySet API
  4. Angular实现数据绑定,它实现原理是什么?
  5. 24种设计模式--抽象工厂模式【Abstract Factory Pattern】
  6. 视频媒体播放,最好的 HTML 解决方法
  7. fedora21安装无线驱动
  8. WebStorm的compass配置
  9. 炫酷线条动画--svg
  10. java多线程的编程实例
  11. MongoDB 3.0新增特性一览
  12. 成熟的 Git 分支模型
  13. 工作了才发现display全忘了
  14. 一线互联网常见的14个Java面试题,你颤抖了吗程序员
  15. 题解 luogu P1144 【最短路计数】
  16. 谷歌浏览器安装octotree插件
  17. 让Source Insight完美支持中文注释
  18. 利用Fiddler和Wireshark解密SSL加密流量
  19. hbase经常使用的shell命令样例
  20. 【Coursera】Fourth Week(1)

热门文章

  1. JAG Practice Contest for ACM-ICPC Asia Regional 2016 C题【贪心】
  2. Unity3D脚本:更改脚本和类名,且不破坏现有脚本引用的方法
  3. 进击python第二篇:初识
  4. ajax请求过程
  5. 集合框架Collection&lt;E&gt;接口
  6. 序列化 jprotobuf
  7. 03.Javascript——入门一些方法记录之Map和Set
  8. sgu316Kalevich Strikes Back(线段树+扫描线)
  9. eCharts基础知识
  10. I/O————字符流和流的关闭