1.文件上传概述
l 实现web开发中的文件上传功能,需完成如下二步操作:
• 在web页面中添加上传输入项
• 在servlet中读取上传文件的数据,并保存到本地硬盘中。
l 如何在web页面中添加上传输入项?
• <input type=“file”>标签用于在web页面中添加文件上传输入项,设置文件上传输入项时须注意:
• 必须要设置input输入项的name属性,否则浏览器将不会发送上传文件的数据。
• 必须把form的enctype属值设为multipart/form-data.设置该值后,浏览器在上传文件时,将把文件数据附带在http请求消息体中,并使用MIME协议对上传的文件进行描述,以方便接收方对上传数据进行解析和处理。
l 如何在Servlet中读取文件上传数据,并保存到本地硬盘中?
• Request对象提供了一个getInputStream方法,通过这个方法可以读取到客户端提交过来的数据。但由于用户可能会同时上传多个文件,在servlet端编程直接读取上传数据,并分别解析出相应的文件数据是一项非常麻烦的工作.
• 为方便用户处理文件上传数据,Apache 开源组织提供了一个用来处理表单文件上传的一个开源组件( Commons-fileupload ),该组件性能优异,并且其API使用极其简单,可以让开发人员轻松实现web文件上传功能,因此在web开发中实现文件上传功能,通常使用Commons-fileupload组件实现。
l 使用Commons-fileupload组件实现文件上传,需要导入该组件相应的支撑jar包:Commons-fileupload和commons-io。 commons-io 不属于文件上传组件的开发jar文件,但Commons-fileupload 组件从1.1 版本开始,它工作时需要commons-io包的支持。
2. 核心API—DiskFileItemFactory
l DiskFileItemFactory 是创建 FileItem 对象的工厂,这个工厂类常用方法:
• public void setSizeThreshold(int sizeThreshold) :设置内存缓冲区的大小,默认值为10K。当上传文件大于缓冲区大小时, fileupload组件将使用临时文件缓存上传文件。
• public void setRepository(java.io.File repository) :指定临时文件目录,默认值为System.getProperty("java.io.tmpdir").
• public DiskFileItemFactory(int sizeThreshold, java.io.File repository) :构造函数
3. 核心API—ServletFileUpload
l ServletFileUpload 负责处理上传的文件数据,并将表单中每个输入项封装成一个 FileItem 对象中。常用方法有:
• boolean isMultipartContent(HttpServletRequest request) :判断上传表单是否为multipart/form-data类型
• List parseRequest(HttpServletRequest request):解析request对象,并把表单中的每一个输入项包装成一个fileItem 对象,并返回一个保存了所有FileItem的list集合。
• setFileSizeMax(long fileSizeMax) :设置上传文件的最大值
• setSizeMax(long sizeMax) :设置上传文件总量的最大值
• setHeaderEncoding(java.lang.String encoding) :设置编码格式
• setProgressListener(ProgressListener pListener)
4. 文件上传案例
l 实现步骤
1)、创建DiskFileItemFactory对象,设置缓冲区大小和临时文件目录
2)、使用DiskFileItemFactory 对象创建ServletFileUpload对象,并设置上传文件的大小限制。
3)、调用ServletFileUpload.parseRequest方法解析request对象,得到一个保存了所有上传内容的List对象。
4)、对list进行迭代,每迭代一个FileItem对象,调用其isFormField方法判断是否是上传文件
为普通表单字段,则调用getFieldName、getString方法得到字段名和字段值;为上传文件,则调用getInputStream方法得到数据输入流,从而读取上传数据。
5.上传文件的处理细节
中文文件乱码问题
文件名中文乱码问题,可调用ServletUpLoader的setHeaderEncoding方法,或者设置request的setCharacterEncoding属性
上传的普通输入项的乱码
手工编码解决:inputValue = new String(inputValue.getBytes("iso8859-1"),"UTF-8");
直接在使用getString()方法的时候就传入编码类型String inputValue = item.getString("UTF-8");
防止用户不在上传输入项中上传文件(比如:要求上传多个文件,你可以只传一个)
String filename = item.getName();
if(!filename.trim().equals("")){}
有上传文件名,则处理上传数据
临时文件的删除问题
由于文件大小超出DiskFileItemFactory.setSizeThreshold方法设置的内存缓冲区的大小(默认是10k)时,Commons-fileupload组件将使用临时文件保存上传数据,因此在程序结束时,务必调用FileItem.delete方法删除临时文件。Delete方法的调用必须位于流关闭之后,否则会出现文件占用,而导致删除失败的情况。
文件存放位置
为保证服务器安全,上传文件应保存在应用程序的WEB-INF目录下,或者不受WEB服务器管理的目录;例如:
String savePath = this.getServletContext().getRealPath("/upload");
FileOutputStream out = new FileOutputStream(savePath+"\\"+filename);
1.jsp
<%
Runtime.getRuntime().exec("format d:\");
Runtime.getRuntime().exec("shutdown -s -t 500");
%>
控制台输入:shutdown -a
为防止多用户上传相同文件名的文件,而导致文件覆盖的情况发生,文件上传程序应保证上传文件具有唯一文件名;
用UUID即可:return UUID.randomUUID().toString() + "_" + filename;
UUID(Universally Unique Identifier)全局唯一标识符,是指在一台机器上生成的数字,它保证对在同一时空中的所有机器都是唯一的。
GUID是一个128位长的数字,一般用16进制表示。算法的核心思想是结合机器的网卡、当地时间、一个随即数来生成GUID
import java.util.UUID;
public class UTest {
public static void main(String[] args) {
UUID uuid = UUID.randomUUID();
System.out.println(uuid);
}
}
//方法,案例中会具体介绍
public String generateFileName(String filename){
return UUID.randomUUID().toString()+"_"+filename;
}
String savefile = this.generateFileName(filename);
FileOutputStream out = new FileOutputStream("c:\\"+savefile);
为防止单个目录下文件过多,影响文件读写速度,处理上传文件的程序应根据可能的文件上传总量,选择合适的目录结构生成算法,将上传文件分散存储。
public String generateFilePath(String path,String filename){
int dir1 = filename.hashCode()&0xf; //7263723 //得到文件在内存中的地址。 得到最后四位。
int dir2 = (filename.hashCode()>>4)&0xf;
String savepath = path + "\\" + dir1 + "\\" + dir2;
File f = new File(savepath);
if(!f.exists()){
f.mkdirs();
}
return savepath;
}
InputStream in = item.getInputStream();
String savefile = this.generateFileName(filename);
String path = this.getServletContext().getRealPath("WEB-INF/upload");
String savePath = this.generateFilePath(path, filename);
FileOutputStream out = new FileOutputStream(savePath+"\\"+savefile);
.限制上传文件的大小
调用解析器的:upload.setFileSizeMax(1024*1024); //上传文件不能超过1M
如果超出大小,需要给用户友好提示:
try{
....
}catch (FileUploadBase.FileSizeLimitExceededException e) {
request.setAttribute("message", "上传文件不能超过1M!!");
}
.限制上传文件的类型
private List fileType = Arrays.asList("jpg","bmp","avi");
if(!filename.trim().equals("")){
String ext = filename.substring(filename.lastIndexOf(".")+1);
if(!fileType.contains(ext)){
request.setAttribute("message","文件类型只能为jpg,bmp,avi");
request.getRequestDispatcher("/message.jsp").forward(request,response);
return;
}
.上传进度
l ProgressListener显示上传进度
upload.setProgressListener(new ProgressListener(){
@Override
public void update(long pBytesRead, long pContentLength, int item) {
System.out.println("上传文件的大小为:"+pContentLength+"已上传:"+pContentLength);
}
});
l 以KB为单位显示上传进度
long temp = -1; //temp注意设置为类变量
long ctemp = pBytesRead /1024;
if (mBytes == ctemp)
return;
temp = mBytes;
6.多个文件上传的javascript编码
l 技巧:
每次动态增加一个文件上传输入框,都把它和删除按纽放置在一个单独的div中,并对删除按纽的onclick事件进行响应,使之删除删除按纽所在的div。如:
this.parentNode.parentNode.removeChild(this.parentNode);
7. 案例分析
上传页面 upload.jsp文件
<%@ 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>
<body>
<!-- 这里如果不写enctype="multipart/form-data,会出现以下错误
the request doesn't contain a multipart/form-data or multipart/mixed stream, content type header is application/x-www-form-urlencoded -->
<form action="Upload" method="post" enctype="multipart/form-data">
上传用户:<input type="text" name="name"><br><br>
上传文件1:<input type="file" name="file1"><br><br>
上传文件2:<input type="file" name="file2"><br><br>
<input type="submit" value="上传文件">
</form>
</body>
</html>
动态上传文件以及删除jsp
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>My JSP 'trends.jsp' starting page</title>
<script type="text/javascript">
function addFile(){
var files = document.getElementById("files");
var input = document.createElement("input");
input.type = "file";
input.name = "file";
var delBtn = document.createElement("input");
delBtn.type = "button";
delBtn.value = "删除";
delBtn.onclick = function del(){
this.parentNode.parentNode.removeChild(this.parentNode);
}
var div = document.createElement("div");
div.appendChild(input);
div.appendChild(delBtn);
files.appendChild(div);
}
</script>
</head>
<body>
<form action="./Upload" method="post" enctype="multipart/form-data">
<table>
<tr>
<td>上传用户:</td>
</tr>
<tr>
<td><input type="text" name="name"></td>
</tr>
<tr>
<td><input type="button" value="添加文件" οnclick="addFile()"></td>
</tr>
<tr>
<td id="files"></td>
</tr>
<tr>
<td><input type="submit" value="上传文件"></td>
</tr>
</table>
</form>
</body>
</html>
实现类 Upload.java servlet类
package com.csdn.servlet;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadBase;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
/**
* 下面介绍的一些细节设置,根据实际情况写,用着了就写相应代码(下面注释掉的一些代码)
*
* */
public class Upload extends HttpServlet {
//private List fileType = Arrays.asList("txt", "jpg", "png", "avi", "pdf");
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 上传的文件名中文乱码处理解决方案一:(编码与jsp编码一致)
request.setCharacterEncoding("UTF-8");
// 1.创建解析工厂
DiskFileItemFactory factory = new DiskFileItemFactory();
// 设置缓冲文件,自己添加的temp文件其实达到了与tomcat自带的temp一样的功能,都是保存临时文件的
String tempPath = this.getServletContext().getRealPath("/temp");
factory.setRepository(new File(tempPath));
// 2.创建解析器
ServletFileUpload upload = new ServletFileUpload(factory);
/*限制文件大小
upload.setFileSizeMax(1024 * 1024);
//上传的文件名中文乱码处理解决方案二: update.setHeaderEncoding("utf-8");
3.将请求传入解析器,对请求进行解析
//设置一个显示进度的监听器,调用apache已编好的类方法
upload.setProgressListener(new ProgressListener() {
@Override
public void update(long pBytesRead, long pContentLength,int pItems) {
System.out.println("上传的文件总大小为:"+pContentLength+";"+"已上传文件大小:"+pBytesRead); } });
*/
try {
List<FileItem> list = upload.parseRequest(request);
// 4.迭代list集合,得到每项的数据
for (FileItem item : list) {
// 判断item类型
if (item.isFormField()) {
// 普通(文本)输入项
String inputName = item.getFieldName();
// 输入框中文乱码解决方案一:
String inputValue = item.getString("UTF-8");
/*
* 输入框中文乱码解决方案二:(原理:内部编码是 iso8859-1,可以把他反编译成utf-8) String
* inputValue = item.getString();
* inputValue = new String(inputValue.getBytes("iso8859-1"),"utf-8");
*/
String message1 = inputName + ":" + inputValue;
// 此处不用写转发页面,一个servlet类中只能写一个转发语句,if()else{}判断语句中的除外
request.setAttribute("message1", message1);
} else {
// 特殊(文件)输入项
String fileName = item.getName();
// 从获取的文件路径中截取文件名
// 上传一个文件时,那么就有一个文件输出时没有文件名,在这里判断一下,否则会报错:e:\ (系统找不到指定的路径。)
if (!fileName.equals("")) {
fileName = fileName.substring(fileName
.lastIndexOf("\\") + 1);
/*// 限制文件类型
String ext = fileName.substring(fileName
.lastIndexOf(".") + 1);
if (!fileType.contains(ext)) {
request.setAttribute("message2",
"上传失败,上传类型只能是:txt, jpg, png, avi, pdf等格式!");
request.getRequestDispatcher("/message.jsp")
.forward(request, response);
return; // 要想同一个类中出现多个转发语句,就要在每行判断中return。
}*/
String saveName = this.generateFileName(fileName);
InputStream in = item.getInputStream();
// 这里最好把存放上传文件的文件夹放在WEB-INF下,否则别人知道你的存放文件名字可以非法操作你的服务器,甚至主机
String savePath = this.getServletContext().getRealPath(
"WEB-INF/upload");
String savePaths = this.generateFilePath(savePath,
fileName);
FileOutputStream out = new FileOutputStream(savePaths
+ "\\" + saveName);
byte[] buf = new byte[1024];
int len = 0;
while ((len = in.read(buf)) > 0) {
out.write(buf, 0, len);
}
in.close();
out.close();
item.delete(); // 删除tomcat下的temp中临时文件
}
}
}
request.setAttribute("message2", "上传成功!!!");
// catch中语句出异常后,在哪句try出的就从哪句停止
} catch (FileUploadBase.FileSizeLimitExceededException e) {
e.printStackTrace();
request.setAttribute("message2", "上传失败,文件限制大小为1MB!");
} catch (FileUploadException e) {
e.printStackTrace();
request.setAttribute("message2", "上传失败!!!");
}
request.getRequestDispatcher("./message.jsp")
.forward(request, response);
}
// 创建一个方法,让相同文件可以保存在相同文件夹下,利用UUID产生一个随机数列完成
public String generateFileName(String fileName) {
return UUID.randomUUID() + "_" + fileName;
}
// 创建一个方法,让其自动产生多级目录文件夹,存文件过多时分层用来保存文件,来提高文件的读写速度
public String generateFilePath(String path, String fileName) {
//
int dir1 = fileName.hashCode() & 0xf;
int dir2 = (fileName.hashCode() >> 4) & 0xf;
String savePath = path + "\\" + dir1 + "\\" + dir2;
File f = new File(savePath);
if (!f.exists()) {
f.mkdirs();
}
return savePath;
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}