工程材料:Commons-fileupload和commons-io两个jar包
链接:http://commons.apache.org/proper/commons-io/download_io.cgi、
http://commons.apache.org/proper/commons-fileupload/download_fileupload.cgi
下载 Binaries 资源
文件上传大致步骤如下:
1、首先设置临时缓冲区(DiskFileItemFactory类工厂)的属性:
存储临界值: setSizeThreshold(1024*100),默认10kb
当内存不够时存至何处: setRepository(tmpFile)
2、缓冲区准备好了,马上ServletFileUpload文件上传解析器利用它来进行文件上传:
通过缓冲区的临界值判断是否需要存入临时文件中,同时监听文件的上传进度,利用解析器的parseRequest(request)方法解析出FORM表单中的每个字段的数据,并将它们分别包装成独立的FileItem对象,然后将这些FileItem对象加入进一个List<FileItem>类型的集合对象中返回。
3、通过FileItem的对象得到上传的文件列表项目Item,之后便Item中的内容全部读入内存中,然后再全部写入外部文件,从而达到了文件上传至服务器,在写入外部文件过程中,注意文件路径、文件名重复、写入编码等问题
文件上传-知识储备:
在HTML页面input 必须有 name <input type="file" name="filename">
表单如果包含一个文件上传输入项的话,这个表单的enctype属性就必须设置为multipart/form-data
<form action ="/day16/servlet/UpLoadServlet" enctype="multipart/form-data" method="post">
浏览器表单的类型如果为multipart/form-data,那么浏览器在提交表单数据时,它将采用MIME协议对数据进行封装后提交,在服务器这端也将不能采用原来传统的方工获取数据了。在服务器端想获取数据就要通过流。request提供了一个getInputStream读取流。
FileItem类的常用方法:
1. boolean isFormField()
isFormField方法用于判断FileItem类对象封装的数据是一个普通文本表单字段,还是一个文件表单字段,如果是普通表单字段则返回true,否则返回false。因此,可以使用该方法判断是否为普通表单域,还是文件上传表单域。
2. String getName()
getName方法用于获得文件上传字段中的文件名。
注意IE或FireFox中获取的文件名是不一样的,IE中是绝对路径,FireFox中只是文件名。
3. String getFieldName()
getFieldName方法用于返回表单标签name属性的值。如上例中<input type="text" name="column" />的value。
4. void write(File file)
write方法用于将FileItem对象中保存的主体内容保存到某个指定的文件中。如果FileItem对象中的主体内容是保存在某个临时文件中,该方法顺利完成后,临时文件有可能会被清除。该方法也可将普通表单字段内容写入到一个文件中,但它主要用途是将上传的文件内容保存在本地文件系统中。
5. String getString()
getString方法用于将FileItem对象中保存的数据流内容以一个字符串返回,它有两个重载的定义形式:
public Java.lang.String getString()
public java.lang.String getString(java.lang.String encoding)
throws java.io.UnsupportedEncodingException
前者使用缺省的字符集编码将主体内容转换成字符串,后者使用参数指定的字符集编码将主体内容转换成字符串。如果在读取普通表单字段元素的内容时出现了中文乱码现象,请调用第二个getString方法,并为之传递正确的字符集编码名称。
6. String getContentType()
getContentType 方法用于获得上传文件的类型,即表单字段元素描述头属性“Content-Type”的值,如“image/jpeg”。如果FileItem类对象对应的是普通表单字段,该方法将返回null。
7. boolean isInMemory()
isInMemory方法用来判断FileItem对象封装的数据内容是存储在内存中,还是存储在临时文件中,如果存储在内存中则返回true,否则返回false。
8. void delete()
delete方法用来清空FileItem类对象中存放的主体内容,如果主体内容被保存在临时文件中,delete方法将删除该临时文件。
尽管当FileItem对象被垃圾收集器收集时会自动清除临时文件,但及时调用delete方法可以更早的清除临时文件,释放系统存储资源。另外,当系统出现异常时,仍有可能造成有的临时文件被永久保存在了硬盘中。
9. InputStream getInputStream()
以流的形式返回上传文件的数据内容。
10. long getSize()
返回该上传文件的大小(以字节为单位)。
为了方便用户处理文件上传数据,Apache开源组织提供了一个用来处理表单文件上传的一个开源组件(Commons-fileupload).使用Commons-fileupload组件实现文件上传,需要导入该组件相应的jar包.
Commons-fileupload和commons-io两个jar包.
DiskFileItemFactory是创建FileItem对象的工厂包括方法:
1.public void setSizeThreshold(int?sizeThreshold)
设置内存缓冲区的大小,默认值为10K,如果文件大于10K,将使用临时文件缓
存上传文件.
2.public void setRepository(Java.io.File repository)
指定临时文件目录
3.public DiskFileItemFactory();
ServletFileUpload 类
ServletFileUpload负责处理上传的文件数据,并将表单中每个输入项封装成一个FileItem对象中.
org.apache.commons.fileupload.servlet.ServletFileUpload类是Apache文件上传组件处理文件上传的核心高级类(所谓高级就是不需要管底层实现,暴露给用户的简单易用的接口)。
使用其parseRequest(HttpServletRequest) 方法可以将通过表单中每一个HTML标签提交的数据封装成一个FileItem对象,然后以List列表的形式返回。使用该方法处理上传文件简单易用。
如果你希望进一步提高新能,你可以采用 getItemIterator 方法,直接获得每一个文件项的数据输入流,对数据做直接处理。
在使用ServletFileUpload对象解析请求时需要根据DiskFileItemFactory对象的属性sizeThreshold(临界值)和repository(临时目录)来决定将解析得到的数据保存在内存还是临时文件中,如果是临时文件,保存在哪个临时目录中?所以,我们需要在进行解析工作前构造好DiskFileItemFactory对象,通过ServletFileUpload对象的构造方法或setFileItemFactory()方法设置ServletFileUpload对象的fileItemFactory属性。
ServletFileUpload类常用方法:
1) public void setSizeMax(long sizeMax)
setSizeMax方法继承自FileUploadBase类,用于设置请求消息实体内容(即所有上传数据)的最大尺寸限制,以防止客户端恶意上传超大文件来浪费服务器端的存储空间。其参数是以字节为单位的long型数字。
在请求解析的过程中,如果请求消息体内容的大小超过了setSizeMax方法的设置值,将会抛出FileUploadBase内部定义的SizeLimitExceededException异常(FileUploadException的子类)。该方法有一个对应的读方法:public long getSizeMax()方法。
2) public void setFileSizeMax(long fileSizeMax)
setFileSizeMax方法继承自FileUploadBase类,用于设置单个上传文件的最大尺寸限制,以防止客户端恶意上传超大文件来浪费服务器端的存储空间。其参数是以字节为单位的long型数字。该方法有一个对应的读方法:public long geFileSizeMax()方法。
在请求解析的过程中,如果单个上传文件的大小超过了setFileSizeMax方法的设置值,将会抛出FileUploadBase内部定义的FileSizeLimitExceededException异常(FileUploadException的子类)。
3) public List parseRequest(javax.servlet.http.HttpServletRequest req)
parseRequest 方法是ServletFileUpload类的重要方法,它是对HTTP请求消息体内容进行解析的入口方法。它解析出FORM表单中的每个字段的数据,并将它们分别包装成独立的FileItem对象,然后将这些FileItem对象加入进一个List类型的集合对象中返回。
该方法抛出FileUploadException异常来处理诸如文件尺寸过大、请求消息中的实体内容的类型不是“multipart/form-data”、IO异常、请求消息体长度信息丢失等各种异常。每一种异常都是FileUploadException的一个子类型。
4) public FileItemIterator getItemIterator(HttpServletRequest request)
getItemIterator方法和parseRequest 方法基本相同。但是getItemIterator方法返回的是一个迭代器,该迭代器中保存的不是FileItem对象,而是FileItemStream 对象,如果你希望进一步提高新能,你可以采用getItemIterator方法,直接获得每一个文件项的数据输入流,做底层处理;如果性能不是问题,你希望代码简单,则采用parseRequest方法即可。
5) public stiatc boolean isMultipartContent(HttpServletRequest req)
isMultipartContent方法方法用于判断请求消息中的内容是否是“multipart/form-data”类型,是则返回true,否则返回false。isMultipartContent方法是一个静态方法,不用创建ServletFileUpload类的实例对象即可被调用。
6) getFileItemFactory()和setFileItemFactory(FileItemFactory)
方法继承自FileUpload类,用于设置和读取fileItemFactory属性。
7) public void setProgressListener(ProgressListener pListener)
设置文件上传进度监听器。该方法有一个对应的读取方法:ProgressListener getProgressListener()。
8) public void setHeaderEncoding()
在文件上传请求的消息体中,除了普通表单域的值是文本内容以外,上传文件中的文件路径名也是文本(包括文件名),在内存中保存的是它们的某种字符集编码的字节数组,Apache文件上传组件在读取这些内容时,必须知道它们所采用的字符集编码,才能将它们转换成正确的字符文本返回。
setHeaderEncoding方法继承自FileUploadBase类,用于设置上面提到的字符编码。如果没有设置,则对应的读方法getHeaderEncoding()方法返回null,将采用HttpServletRequest设置的字符编码,如果HttpServletRequest的字符编码也为null,则采用系统默认字符编码。可以通过一下语句获得系统默认字符编码:
System.getProperty("file.encoding"));
DiskFileItemFactory类
将请求消息实体中的每一个项目封装成单独的DiskFileItem (FileItem接口的实现) 对象的任务
由 org.apache.commons.fileupload.FileItemFactory 接口的默认实现
org.apache.commons.fileupload.disk.DiskFileItemFactory 来完成。当上传的文件项目比较小时,直接保存在内存中(速度比较快),比较大时,以临时文件的形式,保存在磁盘临时文件夹(虽然速度慢些,但是内存资源是有限的)。
属性
1) public static final int DEFAULT_SIZE_THRESHOLD :将文件保存在内存还是磁盘临时文件夹的默认临界值,值为10240,即10kb。
2) private File repository:用于配置在创建文件项目时,当文件项目大于临界值时使用的临时文件夹,默认采用系统默认的临时文件路径,可以通过系统属性 java.io.tmpdir获取。如下代码:
System.getProperty("java.io.tmpdir");
3) private int sizeThreshold:用于保存将文件保存在内存还是磁盘临时文件夹的临界值
构造方法
1) public DiskFileItemFactory()
采用默认临界值和系统临时文件夹构造文件项工厂对象。
2) public DiskFileItemFactory(int sizeThreshold,File repository)
采用参数指定临界值和系统临时文件夹构造文件项工厂对象。
3) FileItem createItem()
根据DiskFileItemFactory相关配置将每一个请求消息实体项目创建成DiskFileItem 实例,并返回。该方法从来不需要我们亲自调用,FileUpload组件在解析请求时内部使用。
4) void setSizeThreshold(int sizeThreshold)
Apache文件上传组件在解析上传数据中的每个字段内容时,需要临时保存解析出的数据,以便在后面进行数据的进一步处理(保存在磁盘特定位置或插入数据库)。因为Java虚拟机默认可以使用的内存空间是有限的,超出限制时将会抛出“java.lang.OutOfMemoryError”错误。如果上传的文件很大,例如800M的文件,在内存中将无法临时保存该文件内容,Apache文件上传组件转而采用临时文件来保存这些数据;但如果上传的文件很小,例如600个字节的文件,显然将其直接保存在内存中性能会更加好些。
setSizeThreshold方法用于设置是否将上传文件已临时文件的形式保存在磁盘的临界值(以字节为单位的int值),如果从没有调用该方法设置此临界值,将会采用系统默认值10KB。对应的getSizeThreshold() 方法用来获取此临界值。
5) void setRepository(File repository)
setRepositoryPath方法用于设置当上传文件尺寸大于setSizeThreshold方法设置的临界值时,将文件以临时文件形式保存在磁盘上的存放目录。有一个对应的获得临时文件夹的 File getRespository() 方法。
注意:当从没有调用此方法设置临时文件存储目录时,默认采用系统默认的临时文件路径,可以通过系统属性 java.io.tmpdir 获取。如下代码:
System.getProperty("java.io.tmpdir");
Tomcat系统默认临时目录为“<tomcat安装目录>/temp/”。
包结构:
代码如下:
FileUphanderServlet类:
package FileUpAndDown;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.UUID;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
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.ProgressListener;
import org.apache.commons.fileupload.disk.DiskFileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import com.sun.xml.internal.bind.v2.schemagen.xmlschema.List;
/**
* Servlet implementation class FileUphanderServlet
*/
@WebServlet("/FileUphanderServlet")
public class FileUphanderServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* @see HttpServlet#HttpServlet()
*/
public FileUphanderServlet() {
super();
// TODO Auto-generated constructor stub
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
response.getWriter().append("Served at: ").append(request.getContextPath());
//将文件保存在WEB-INF文件夹下,保证文件安全性,不允许外界直接访问
/*
* 此处特别注意:getServletContext()是获取服务器中当前WebRoot的物理路径
*是在.metdata隐藏文件夹下面的项目文件路径下面
*所以在验证是否上传成功时,查看本地项目文件目录的下的uplod或者temp文件夹,是找不到上传的文件的,因为根本不存储在该文件夹内
*可以通过打印出文件在服务器的路径,再到磁盘中去查找
*/
String savePath = this.getServletContext().getRealPath("/WEB-INF/upload");
//上传时生成临时文件的文件夹,临时文件的用处在于,当上传的文件大于内存大小时,将文件转移入临时文件,最有转入正式文件中
String tempPath = this.getServletContext().getRealPath("/WEB-INF/temp");
System.out.println("临时文件路径为 "+tempPath);
File tmpFile = new File(tempPath);
if (!tmpFile.exists()) {
tmpFile.mkdirs();
System.out.println("文件夹不存在,已为您创建");
}
String message = "";
try {
//使用Apache文件上传组件上传文件步骤
//1、创建一个DiskFileItemFactory工厂
DiskFileItemFactory factory = new DiskFileItemFactory();
//2、设置工厂缓冲区的大小,默认是10kb
factory.setSizeThreshold(1024*100);//设置为100kb
//3.当文件大于内存时,存放于临时文件,设置上传时临时文件的保存路径
factory.setRepository(tmpFile);
//4.创建文件上传解析器
ServletFileUpload upload = new ServletFileUpload(factory);
//5.监听文件上传进度
upload.setProgressListener(new ProgressListener() {
@Override
public void update(long pBytesRead, long pContentLength, int arg2) {
// TODO Auto-generated method stub
System.out.println("文件上传大小为:"+pContentLength+"当前进度为:"+pBytesRead);
}
});
//解决上传文件名的中文乱码问题
upload.setHeaderEncoding("utf-8");
//判断请求消息中的内容是否是“multipart/form-data”类型,是则返回true,否则返回false。
if (!ServletFileUpload.isMultipartContent(request)) {
//按照传统方式获取数据
return;
}
//设置的单个文件上传最大值,为1024*1024,即1M
upload.setFileSizeMax(1024*1024);
//设置文件上传总量的最大值
upload.setSizeMax(1024*1024*10);
java.util.List<FileItem> list = null;
//使用ServletFileUpload解析器上传数据,解析结果返回List<FileItem>集合,每一个FileItem对应一个form表项
list = upload.parseRequest(request);
//循环遍历list
for(FileItem item:list) {
//如果是普通输入项数据,即文本框中只是单纯的文字信息
if (item.isFormField()) {
//获取表单标签的属性名
String name = item.getFieldName();
//解决中文乱码问题
String value = item.getString("utf-8");
System.out.println(name +"="+value);
}else {//如果上传的是文件
//获取文件上传字段的文件名
String filename = item.getName();
System.out.println(filename);
if (filename==null||filename.trim().equals("")) {
continue;//如果文件为空,则跳过当前循环
}
//获取文件名之后,只保留文件名部分,"\\"中第一个"\"为转义字符,+1,表示在碰到\时,后移一位开始取值
filename = filename.substring(filename.lastIndexOf("\\")+1);
//得到上传文件的扩展名,此操作目的是得到扩展名控制上传文件的类型
String fileExname = filename.substring(filename.lastIndexOf(".")+1);
System.out.println("上传文件类型为"+fileExname);
//得到item上传文件中的输入流,以便将内容读入内存
InputStream in = item.getInputStream();
//得到文件保存的名字
String saveFilename = makeSaveFile(filename);
//得到文件的保存目录
String realSavePath = makePath(saveFilename, savePath);
//创建一个文件输出流,以便将内容写入外部文件
FileOutputStream out = new FileOutputStream(realSavePath + "\\" + saveFilename);
System.out.println("在服务器中的文件路径:"+realSavePath + "\\" + saveFilename);
//创建一个缓冲区
byte buffer[] = new byte[1024];
//判断输入流中的数据是否已经读完的标识
int len = 0;
//循环将输入流读入到缓冲区当中,(len=in.read(buffer))>0就表示in里面还有数据
while((len=in.read(buffer))>0){
//使用FileOutputStream输出流将缓冲区的数据写入到指定的目录(savePath + "\\" + filename)当中
out.write(buffer, 0, len);
}
//关闭输入流
in.close();
//关闭输出流
out.close();
//删除处理文件上传时生成的临时文件
item.delete();
message = "文件上传成功!";
}
}
} catch (FileUploadBase.FileSizeLimitExceededException e) {
e.printStackTrace();
request.setAttribute("message", "单个文件超出最大值!!!");
request.getRequestDispatcher("jsp/message.jsp").forward(request, response);
return;
} catch (FileUploadBase.SizeLimitExceededException e) {
e.printStackTrace();
request.setAttribute("message", "上传文件的总的大小超出限制的最大值!!!");
request.getRequestDispatcher("jsp/message.jsp").forward(request, response);
return;
}catch (Exception e) {
message= "文件上传失败!";
e.printStackTrace();
}
request.setAttribute("message",message);
request.getRequestDispatcher("jsp/message.jsp").forward(request, response);
}
/**
* 为防止一个目录下面出现太多文件,要使用hash算法打散存储
*
* @param filename 文件名,要根据文件名生成存储目录
* @param savePath 文件存储路径
* @return 新的存储目录
*/
private String makePath(String filename,String savePath){
//得到文件名的hashCode的值,得到的就是filename这个字符串对象在内存中的地址
int hashcode = filename.hashCode();
int dir1 = hashcode&0xf; //0--15
int dir2 = (hashcode&0xf0)>>4; //0-15
//构造新的保存目录
String dir = savePath + "\\" + dir1 + "\\" + dir2; //upload\2\3 upload\3\5
//File既可以代表文件也可以代表目录
File file = new File(dir);
//如果目录不存在
if(!file.exists()){
//创建目录
file.mkdirs();
}
return dir;
}
private String makeSaveFile(String filename) {
//为防止文件覆盖的现象发生,要为上传文件产生一个唯一的文件名
return UUID.randomUUID().toString() + "_" + filename;
}
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
response.setContentType("text/html;charset=utf-8");//设置响应到页面的文本编码
request.setCharacterEncoding("utf-8");//设置客户端传过来的字符编码
doGet(request, response);
}
}
upload.jsp:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>文件上传</title>
</head>
<body>
<!-- 表单如果包含一个文件上传输入项的话,这个表单的enctype属性就必须设置为multipart/form-data -->
<form action="${pageContext.request.contextPath}/FileUphanderServlet" 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>
</body>
</html>
message.jsp:
<%@ page language="java" pageEncoding="UTF-8"%>
<!DOCTYPE HTML>
<html>
<head>
<title>消息提示页面</title>
</head>
<body>
${message}
</body>
</html>