一.Tip:上传文件的处理细节 http://blog.sina.com.cn/s/blog_3eb047df0100ow34.html
1.中文文件乱码问题
文件名中文乱码问题,可调用ServletUpLoader的setHeaderEncoding方法,或者设置request的setCharacterEncoding属性
普通字段的乱码:
1.手工转换
2.FileItem.getString("UTF-8");获得名称的时候传一个字符集
上传文件的乱码: ServletFileUpload.setHeaderEncoding("UTF-8");
2.临时文件的删除问题
由于文件大小超出DiskFileItemFactory.setSizeThreshold方法设置的内存缓冲区的大小时,Commons-fileupload组件将使用临时文件保存上传数据,因此在程序结束时,务必调用FileItem.delete方法删除临时文件。
在处理完每一个item之后,要记得调用item.delete方法,删除临时文件
Delete方法的调用必须位于流关闭之后,否则会出现文件占用,而导致删除失败的情况。
3.文件存放位置
为保证服务器安全,上传文件应保存在应用程序的WEB-INF目录下,或者不受WEB服务器管理的目录。
为防止多用户上传相同文件名的文件,而导致文件覆盖的情况发生,文件上传程序应保证上传文件具有唯一文件名。
为防止单个目录下文件过多,影响文件读写速度,处理上传文件的程序应根据可能的文件上传总量,选择合适的目录结构生成算法,将上传文件分散存储。
4.如何处理空文件上传
在处理item时,判断item的getName方法返回的是不是"",如果为空,则代表客户机没有上传文件
5.限制上传文件的最大值
限制单个的最大值:upload.setFileSizeMax(1024*1024); FileUploadBase.FileSizeLimitExceededException
限制多个的最大值:upload.setSizeMax(1024*1024*5); FileUploadBase.SizeLimitExceededException
6.为防止多用户上传相同文件名的文件,而导致文件覆盖的情况发生,文件上传程序应保证上传文件具有唯一文件名。
public String generateFilename(String filename){
return UUID.randomUUID().toString() + "_" + filename; //83743_9834834_9u34_9834-1.avi
}
7.为避免在一个目录保存多个文件,影响文件读取的性能,应把文件打散到多个目录存储
public String generateFilepath(String uploadpath,String filename){ //upload
int hashcode = filename.hashCode();
int dir1 = hashcode&0xf;
int dir2 = (hashcode>>4)&0xf;
// http:// c:\\
String savepath = uploadpath + "\" + dir1 + "\" + dir2;
File file = new File(savepath);
if(!file.exists()){
file.mkdirs();
}
return savepath;
}
8.限制上传文件的类型
得到上传文件的后缀名,判断是否合法,如果不合法,则抛异常提醒用户
9.ProgressListener显示上传进度
ProgressListener progressListener = new ProgressListener() {
public void update(long pBytesRead, long pContentLength, int pItems) {
System.out.println("到现在为止, " + pBytesRead + " 字节已上传,总大小为 "
+ pContentLength);
}
};
upload.setProgressListener(progressListener);
以KB为单位显示上传进度
long temp = -1; //temp注意设置为类变量
long ctemp = pBytesRead /1024;
if (mBytes == ctemp)
return;
temp = mBytes;
10.做出动态上传效果
实例
public class UploadServlet4 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//request.setCharacterEncoding("UTF-8");这个解决不了上传文件的乱码问题
List exts = Arrays.asList("jpg","bmp"); //创建一个集合,封装限制上传文件类型数据
try{
//1.创建工厂
DiskFileItemFactory factory = new DiskFileItemFactory();
//设置缓冲文件夹路径
factory.setRepository(new File(this.getServletContext().getRealPath("/temp")));
//2.得到解析器
ServletFileUpload upload = new ServletFileUpload(factory);
//设置解析器的字符集
upload.setHeaderEncoding("UTF-8");
//设置单个上传文件最大容量
upload.setFileSizeMax(1024*1024);
//设置多个上传文件最大容量
upload.setSizeMax(1024*1024*5);
//设置监听器,创建一个接口,实现接口方法,从监听器中获得:上传文件的当前上传字节pBytesRead,上传文件总大小pContentLength,正在处理的上传对象pItems
upload.setProgressListener(new ProgressListener(){
public void update(long pBytesRead, long pContentLength, int pItems) {
System.out.println("当前处理的是" + pItems + "上传文件,文件大小是: " + pContentLength + ",当前已上传:" + pBytesRead + "字节");
}
});
//3.用解析器解析request,得到代表每一个输入项的fileitem对象
List<FileItem> list = upload.parseRequest(request);
//4.迭代集合,获取每一个fileitem的数据
for(FileItem item : list){
if(item.isFormField()){
//代表当前获取的普通输入项的数据
String inputName = item.getFieldName(); //username
String inputValue = item.getString("UTF-8"); //在获得上传数据时通过参数直接给字符集相当于下面代码
//inputValue = new String(inputValue.getBytes("iso8859-1"),"UTF-8");
System.out.println(inputName + "=" + inputValue);
}else{ //代表当前获取的item封装的是上传文件的数据
//获得上传文件的文件路径
String filename = item.getName(); //""
//判断上传文件路径为空的情况
if(filename.trim().equals("")){
continue;
}
//根据上传文件名获得文件类型,判断exts集合中是否允许该类型上传,如果不支持,抛一个自定义异常
String ext = filename.substring(filename.lastIndexOf(".")+1);
if(!exts.contains(ext)){ //集合的是否包含方法
throw new ExtException();
}
//如果支持,获得文件名
filename = filename.substring(filename.lastIndexOf("\")+1); //""
//获得上传对象的输入流读取文件输出到指定位置
InputStream in = item.getInputStream();
int len = 0;
byte buffer[] = new byte[1024];
//输出路径
String uploadpath = this.getServletContext().getRealPath("/WEB-INF/upload");
//传入文件名返回一个随即生成带ID的唯一保存文件名
String saveFilename = generateFilename(filename);
//传入输出路径和保存文件名,根据hash算法创建目录,返回一个用于将传入文件打散保存的保存路径
String savePath = generateFilepath(uploadpath, saveFilename);
//创建输出流到指定保存地址
FileOutputStream out = new FileOutputStream(savePath + "\" + saveFilename);
while((len=in.read(buffer))>0){
out.write(buffer, 0, len);
}
in.close();
out.close();
item.delete(); //删除item对应的临时文件
}
}
request.setAttribute("message", "上传成功!!");
}catch (FileUploadBase.FileSizeLimitExceededException e) {
request.setAttribute("message", "上传文件不能超过1M");
}catch (FileUploadBase.SizeLimitExceededException e) {
request.setAttribute("message", "上传文件的大小加起来不能超过5M");
}catch (ExtException e) {
request.setAttribute("message", "上传文件只能是jpg,bmp");
}catch (Exception e) {
e.printStackTrace();
request.setAttribute("message", "由于未知错误上传失败!!");
}
request.getRequestDispatcher("/message.jsp").forward(request, response);
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
//需要的更具类方法,本应该写在工具类中,这里为了方便直接写在servlet里
//根据传入的文件名生成一个带ID的唯一的文件名返回
public String generateFilename(String filename){
return UUID.randomUUID().toString() + "_" + filename; //83743_9834834_9u34_9834-1.avi
}
//获得打散保存路径方法
public String generateFilepath(String uploadpath,String filename){ //upload
//根据传进来的文件名算出hash值
int hashcode = filename.hashCode();
//获得hash值的后4位的int数作为1级保存目录
int dir1 = hashcode&0xf;
//将hash值右移4位,也就是获取5到8位,作为2级保存目录
int dir2 = (hashcode>>4)&0xf;
// http:// c:\\ 根据级目录组合成一个为文件保存的目录
String savepath = uploadpath + "\" + dir1 + "\" + dir2;
//创建文件流,将保存地址传入,如果不存在则创建其所有目录,最后返回该目录地址
File file = new File(savepath);
if(!file.exists()){
file.mkdirs();
}
return savepath;
}
}
JSP编写动态上传效果
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>动态上传</title>
<script type="text/javascript">
function add(){
var input = document.create_rElement("input");
input.type = "file";
input.name = "file";
var btn = document.create_rElement("input");
btn.type = "button";
btn.value = "删除";
btn.onclick = function del(){
<!--获得这个节点的父节点的父节点,删除其孩子节点(这个节点的父节点),这样就删除了整个DIV-->
this.parentNode.parentNode.removeChild(this.parentNode);
}
var div = document.create_rElement("div");
div.a(input);
div.a(btn);
document.getElementByIdx_x("files").a(div);
}
</script>
</head>
<body>
<form action="${pageContext.request.contextPath }/servlet/UploadServlet4" method="post" enctype="multipart/form-data">
<table>
<tr>
<td>上传用户:</td>
<td>
<input type="text" name="username">
</td>
</tr>
<tr>
<td></td>
<td>
<input type="button" value="添加文件" οnclick="add()">
</td>
</tr>
<tr>
<td></td>
<td>
<div id="files">
</div>
</td>
</tr>
<tr>
<td></td>
<td>
<input type="submit" value="上传">
</td>
</tr>
</table>
</form>
</body>
</html>