文件的上传
1.什么是文件上传?
将客户端的文件通过流写入到服务器的过程
2.文件上传技术
① JSPSmartUpload :应用在JSP上的文件上传和下载的组件。
② FileUpload :应用在Java环境上的文件上传的功能。
③ Servlet3.0 :提供文件上传的功能
④ Struts2 :提供文件上传的功能
3.文件上传的三个前提
①表单的提交方式必须是POST(因为GET提交方式是通过URL提交数据,有大小的限制)
②表单的提交是通过<input type="file">元素,需要有name属性和值。
③表单enctype="multipart/form-data"(如果没有设置enctype,那么提交的请求体只有文件名)
4.文件上传原理分析
这是文件上传的请求部分,Content-Type设置multipart/form-data属性后会生成一个分割线,上传的文件会按照这个分割线分开,每一个部分都是独立的文件。
5.用FileUpload上传文件
FileUpload是Apach commons下面的一个子项目,用来实现java环境下的文件上传功能,所以在使用FileUpload之前需要导入jar包。
先建立一个上传界面
<%@ 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>Insert title here</title>
</head>
<body>
<h1>文件上传</h1>
<h3>${ msg }</h3>
<!-- 文件上传三要素
* 表单需要是post提交
* 表单中需要文件上传项,必须有name的属性和值
* 表单的enctype属性必须是multipart/form-data
-->
<form action="${ pageContext.request.contextPath }/UploadServlet" method="post" enctype="multipart/form-data">
文件描述:<input type="text" name="info"><br/>
文件上传:<input type="file" name="upload"><br/>
<input type="submit" value="上传"/>
</form>
</body>
</html>
编写Servlet
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
try {
// 创建磁盘文件项工厂
DiskFileItemFactory diskFileItemFactory = new DiskFileItemFactory();
// 创建解析核心类
ServletFileUpload servletFileUpload = new ServletFileUpload(diskFileItemFactory);
// 解析request请求
List<FileItem> list = servletFileUpload.parseRequest(request);
for (FileItem fileItem : list) {
// 判断是否是文件上传项
if (fileItem.isFormField()) {
// 普通项
String fieldName = fileItem.getFieldName();
String value = fileItem.getString();
System.out.println(fieldName + " " + value);
} else {
// 文件上传项
//获取服务器端接收上传文件的路径
String path = this.getServletContext().getRealPath("/upload");
//获取文件名称
String fileName = fileItem.getName();
//调用getInputStream()方法将上传文件写入输入流中
InputStream is = fileItem.getInputStream();
//构建输出流,路径为文件夹+文件名
OutputStream os = new FileOutputStream(path + "\\" + fileName);
//输入流写入输出流
IOUtils.copy(is, os);
is.close();
os.close();
}
}
前面提到上传的文件都是根据分割线切割获取的,这些文件被包装成一个个FileItem对象,遍历判断这些FileItem对象是普通项还是文件上传,如果是普通项就调用getFiledName()获取name属性,调用getString()获取value属性。如果是文件上传项,我们需要做的就是将文件以输入流的形式获取到,再用输出流输出到服务器的文件中。
这里涉及到三个对象:DiskFileItemFactory,ServletFileUpload,FileItem。
(1)DiskFileItemFactory:磁盘文件项工厂
主要方法:void setSizeThreshold(int sizeThreshold):设置文件上传缓冲区的大小,默认是10kb。
void setRepository(java.io.File repository):设置临时文件的存放路径。
(2)ServletFileUpload:核心解析类
主要方法:
①static boolean isMultipartContext(javax.servlet.http.HttpServletRequest request):判断表单的enctype属性是否正确。
★ ②java.util.List parseRequest(javax.servlet.http.HttpServletRequest request):解析request对象,返回一个List集合(每个部分都是一个FileItem对象)。
★ ③void setFileSizeMax(long fileSizeMax):设置单个文件上传大小。
★④void setSizeMax(long sizeMax):设置总的上传文件大小。
★⑤void setHeaderEncoding(String encoding):设置中文文件名上传乱码问题。
(3)FileItem文件对象
主要方法:
①boolean isFromField():判断表单项是普通项还是文件上传项。true为普通项
普通项的方法:
①String getFieldName():获取普通项的名称。
②String getString():获得普通项的值。
文件上传项:
①String getName():获取文件上传的文件名。
②InputString getInputString():获取文件上传的内容。
③long getSize():获取上传文件的大小。
④void delete():删除文件上传过程中的临时文件。
6.文件上传过程中可能出现的问题以及解决办法
(1)文件上传兼容浏览器的问题
问题描述:当采用老版本的IE浏览器时,会出现调用getName()方法时获取的文件名是带路径的(例:C:\User\jt\a.txt),而我们只需要a.txt。
解决方案:判断文件名中是否存在“\”,如果存在则表明文件名是带路径名的,这个时候就应该查询到最后一个“\”出现的位置,在对文件名进行截取。
String filename = fileItem.getName();
//解决老版本浏览器带来的路径问题
//将文件名按照路径分隔符切割
int idx = filename.lastIndexOf("\\");//第一个\是转义字符
if(idx!=-1){
//从"\"后一位索引开始截取字符串
filename = filename.substring(idx+1);
}
(2)文件名重复问题
问题描述:当服务器接收来自各个客户端的文件时,难免会遇到文件名重复的情况。这个时候后上传的文件就会将之前的文件覆盖掉。
解决方案:在文件上传到服务器的后需要被重新命名,这个名称的特点是唯一性,UUID是一种软件建构的标准,其目的是让分布式系统中的所有元素,都能有唯一的辨识信息。在java环境中提供了UUID类实现这个功能。
public static String getUuidFilename(String filename){
//获取文件的后缀名
int idx = filename.lastIndexOf(".");
String suffix = filename.substring(idx);
//使用UUID对象生成随机字符串作为文件名前缀
String prefix = UUID.randomUUID().toString().replace("-", "");
//将文件名和后缀名拼接并返回
return prefix+suffix;
}
通过以上方法就可以生成一组32位的16进制数字构成的UUID码作为文件名。 (3)同一文件夹下文件数量过多问题
问题描述:现在所有的用户都上传文件,如果网站访问量比较大,如果都上传到同一个目录下,在同一个目录下存放的文件太多了,也会对程序有影响(其实打开该目录的时候,都会很卡,更别说读写操作)。
解决方案:目录分离:按时间分离(月,周,天,小时),按用户分离(张三,李四),按个数分离(一个目录下存放300个文件),按目录分离算法(按照某种特定的算法进行分离)。
目录分离算法实现:
①获取唯一文件名(32位16进制数组成)
②根据唯一文件名获取hashCode值(int类型的值 32位)。
③让hashCode的值&0xf;(得到的这个值作为一级目录)。
④让hashCode的值无符号右移四位再&0xf;(得到的值作为二级目录)。
public static String getRealPath(String fileName){
int code1 = fileName.hashCode();
int d1 = code1&0xf;
int code2 = d1>>>4;
int d2 = code2&0xf;
return "/"+d1+"/"+d2;
}
将生成的目录作为路径返回,在拼接到服务器的文件夹目录下。
总结:解决了上述问题后,我们的文件上传就可以完善成下面的代码
public class UploadServlet extends HttpServlet {
/**
* 文件上传Servlet
*/
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
try {
// 创建磁盘文件项工厂
DiskFileItemFactory diskFileItemFactory = new DiskFileItemFactory();
// 创建解析核心类
ServletFileUpload servletFileUpload = new ServletFileUpload(diskFileItemFactory);
// 解析request请求
List<FileItem> list = servletFileUpload.parseRequest(request);
for (FileItem fileItem : list) {
// 判断是否是文件上传项
if (fileItem.isFormField()) {
// 普通项
String fieldName = fileItem.getFieldName();
String value = fileItem.getString();
System.out.println(fieldName + " " + value);
} else {
// 文件上传项
//获取文件上传项的名称
String filename = fileItem.getName();
//解决老版本浏览器带来的路径问题
//将文件名按照路径分隔符切割
int idx = filename.lastIndexOf("\\");
if(idx!=-1){
filename = filename.substring(idx+1);
}
//生成唯一文件名
String uuidFilenameName = UploadUtils.getUuidFilename(filename);
//获取文件上传数据
InputStream is = fileItem.getInputStream();
//获取文件上传到磁盘的绝对路径
String realPath = this.getServletContext().getRealPath("/upload");
//避免文件传到同一目录下引起的文件过多问题(目录分离)
String path = UploadUtils.getRealPath(filename);
String newPath = realPath+path;
File file = new File(newPath);
//判断文件夹是否存在
if(!file.exists()){
file.mkdirs();
}
//将文件写入到newPath文件夹下的realName文件名中
OutputStream os = new FileOutputStream(newPath+"/"+uuidFilenameName);
IOUtils.copy(is, os);
is.close();
os.close();
}
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
工具类UploadUtils的代码public class UploadUtils {
private UploadUtils(){
}
public static String getUuidFilename(String filename){
//获取文件的后缀名
int idx = filename.lastIndexOf(".");
String suffix = filename.substring(idx);
//使用UUID对象生成随机字符串作为文件名前缀
String prefix = UUID.randomUUID().toString().replace("-", "");
return prefix+suffix;
}
public static String getRealPath(String fileName){
//根据传入的唯一文件名生成hashcode码
int code1 = fileName.hashCode();
//生成一级目录
int d1 = code1&0xf;
int code2 = d1>>>4;
//生成二级目录
int d2 = code2&0xf;
return "/"+d1+"/"+d2;
}
}