一、文件上传
实现 Web 开发中的文件上传功能,两个操作:在 Web 页面添加上传输入项,在 Servlet 中读取上传文件的数据并保存在本地硬盘中。
1、Web 端上传文件。在 Web 页面中添加上传输入项: 设置文件上传输入项时应注意:(1) 必须设置 input 输入项的 name 属性,否则浏览器将不会发送上传文件的数据。(2) 必须把 form 的 enctype 属性设为 multipart/form-data,设置该值后,浏览器在上传文件时,将把文件数据附带在 http 请求消息体中,并使用 MIME 协议对上传文件进行描述,以方便接收方对上传数据进行解析和处理。(3) 表单提交的方式要是 post
2、服务器端获取文件。如果提交表单的类型为 multipart/form-data 时,就不能采用传统方式获取数据。因为当表单类型为 multipart/form-data 时,浏览器会将数据以 MIME 协议的形式进行描述。如果想在服务器端获取数据,那么我们必须采用获取请求消息输入流的方式来获取数据。
3、Apache-Commons-fileupload。为了方便用户处理上传数据,Apache 提供了一个用来处理表单文件上传的开源组建。使用 Commons-fileupload 需要 Commons-io 包的支持。
4、fileuplpad 组建工作流程
(1)客户端将数据封装在 request 对象中。
(2)服务器端获取到 request 对象。
(3)创建解析器工厂 DiskFileItemFactory 。
(4)创建解析器,将解析器工厂放入解析器构造函数中。之后解析器会对 request 进行解析。
(5)解析器会将每个表单项封装为各自对应的 FileItem。
(6)判断代表每个表单项的 FileItem 是否为普通表单项 isFormField,返回 true 为普通表单项。
(7)如果是普通表单项,通过 getFieldName 获取表单项名,getString 获得表单项值。
(8)如果 isFormField 返回 false 那么是用户要上传的数据,可以通过 getInputStream 获取上传文件的数据。通过getName 可以获取上传的文件名。
5、DiskFileItemFactory。DiskFileItemFactory 是创建 FileItem 对象的工厂,常用的方法有:
setSizeThreshold(int sizeThreshold,File repository ) 设置内存缓冲区的大小,默认值为 10k 当上传文件大于缓冲区大小时,fileupload 组建将使用临时文件缓存上传文件。还可以设置临时文件目录。
setRepository(File repository) 指定临时文件目录默认值为 System.getProperty("java.io.tmpdir")
6、ServletFileUpload。ServletFileUpload负责处理上传的文件数据,并将表单中每个输入项封装成一个 FileItem 对象中,常用方法有:
isMultipartContent(HttpServletRequest request) 判断上传表单是否为 multipart/form-data。
parseRequest(HttpServletRequest request) 解析 request 对象,并把表单中的每一个输入项包装成一个 fileItem 对象,并反悔一个保存了所有 FileItem 的 List 集合。
setFileSizeMax(long fileSizeMax) 设置上传文件的最大值。
setSizeMax(long sizeMax) 设置上传文件总量的最大值。
setHeaderEncoding(String encoding) 设置编码方式。
setProgressListener(ProgressListener pListener) 指定一个监听器。
package upload;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
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.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
@SuppressWarnings ( "serial" )
public class UploadServlet extends HttpServlet {
@SuppressWarnings ( "unchecked" )
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
DiskFileItemFactory factory = new DiskFileItemFactory();
factory.setRepository(new File( "d:/temp" ));
ServletFileUpload upload = new ServletFileUpload(factory);
if (ServletFileUpload.isMultipartContent(request)) {
try {
List list = upload.parseRequest(request);
for (FileItem item : list) {
if (item.isFormField()) {
System.out.println("普通字段:"
+ item.getString());
} else {
InputStream in = item.getInputStream();
String fileName = item.getName();
FileOutputStream fos = new FileOutputStream( this .getServletContext().getRealPath( "/WEB-INF/upload" )+ "/" + fileName);
int len = 0 ;
byte [] b = new byte [ 1024 ];
while ((len = in.read(b)) != - 1 ) {
fos.write(b, 0 , len);
}
fos.close();
}
}
} catch (FileUploadException e) {
request.setAttribute("message" , "上传失败。。" );
request.getRequestDispatcher("/message" ).forward(request,
response);
}
}
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
FileItem.getFieldName() 获取的是普通表单项的表单项名。
FileItem.getString(encod) 获取的是普通表单项的表单项值。
FileItem.getName() 获取的是上传文件的文件名,如果是 IE6 则获取的是全路径,否则获取的是简单文件名。
7、上传文件的细节问题
(1)如果将服务器的上传目录暴露在外界能访问到的地方,那么用户可以写一个系统命令文件传至服务器,只有再访问执行。为了解决这个问题,有两种途径,一:将上传目录放在 WEB-INF 中。 二:放在服务器管理不到的目录内。
(2)上传文件的乱码问题
一:普通表单项乱码,因为上传文件的 enctype 为 multipart/form-data 所以我们使用传统的 setCharacterEncoding 是不可行的。所以我们在获取到值的时候,必须先将其按照 ISO-8859-1 的形式转为字节,再按照 UTF-8 的形式转为字符。1 手工解决:String value = fileItem.getString("username"); value = new String(value.getBytes("iso-8859-1") ,"UTF-8"); 这是一种方法。2 组建解决:还可以使用 fileItem.getString("UTF-8") 来直接解决普通表单项的乱码。
二:上传文件的文件名乱码,通过解析器的方法更改字符编码。servletFileUpload.setHeaderEncoding("UTF-8");
(3)没有选择文件空提交问题
判断文件名是否为空 String filename = fileItem.getName(); 判断 filename 是否为空,如果为空则不处理。
(4)限制上传文件类型,判断文件是否为合法文件
通过截取文件扩展名,来匹配服务器中的集合,如果包含则允许执行上传,如果不包含则抛一个自定义编译时异常,在 catch 代码块中往 request 域中添加显示信息,转发到 Jsp 中给用户提示。
(5)限制上传文件大小
通过解析器来限定上传文件大小,servletFileUpload.setFileSizeMax(1024*1024); 其中限制的单位为 byte。如果超出限制大小,则抛出 FileSizeLimitExceedException 编译时异常。捕获异常的处理方式和上面一样。
(6)防止上传文件被覆盖的问题
服务器在保存每一个上传文件时,要为每一个文件生成一个唯一的文件名。我们采用 UUID + 真实文件名作为服务器存储的文件名。sdfe-123k-wdf3-sdfe4_1.jpg 。
(7)防止服务器一个存储目录文件数过多造成访问速度下降的问题
在 windows 操作系统下,如果一个目录中的文件数超过 1000 就会导致该目录的访问速度下降。为了解决这个问题,大致有两种算法
一:将文件名进行 Base64 编码,首先取得编码后文件名的第一个字母,如果是 a 则存放在 a 目录中,如果是 b 则存放在 b 目录中,以此类推,可以分为 26 个目录存放,26 个目录每个目录存放 1000 个文件,那么可以存放 2.6 万个文件。如果都存满了,那么我们再取出文件名的第二个字母,如果一个文件前两个字母是 ac 那么我们将文件存放在 a 目录下面的 c 目录下,这样每个目录下又有了 26 个子目录,再以此类推 26*1000*26*1000*26*1000……这样存下去,我们可以将世界上的所有文件存放在服务器中,而且保证了服务器中的每个文件夹的文件个数不超过 1000。
二:将获取到的文件名进行 hash 运算,可以得到一个 32 位的整数,我们把 32 位整数与 0xF 进行按位与运算,可以得到第一个字节,这个字节可以表示 0-15 之间的任意数字,总共可以表示 16 个数,我们取出这个数作为服务器保存文件的第一个目录,之后我们再将之前的 32 位整数右移 4 位,可以得到第二个字节的 4 个二进制位,之后再与 0xF 位与运算,就得到了第二个 0-15 之间的数,我们用这个数作为二级目录,那么我们现在可以存放的文件总数就是 16*16*1000 如果不够的话,我们可以取出第三个字节,以此类推,我们可以得到足够多的目录来存放文件。
public String makeDir(String serverDir, String fileName){
int hashCode = fileName.hashCode();
int dir_1 = hashCode& 0xf ;
int dir_2 = (hashCode>> 4 )& 0xf ;
String path = serverDir + File.separator + dir_1 + File.separator + dir_2;
File dir = new File(path);
if (!dir.exists()){
dir.mkdirs();
}
return path + File.separator + fileName;
}
(8)注意临时文件删除的问题
解析器在解析 request 的上传文件时,如果文件大小超过缓冲区的大小,那么将采用临时文件缓存上传文件,如果在执行完上传以后,没有及时删除临时文件,则会导致服务器中保存了两份已上传的文件。所以我们在执行完上传,或者上传异常处理中,应该及时删除临时文件。fileItem.delete()
(9)通过监听器实现上传文件进度显示
在解析器解析 request 之前,可以通过解析器设置一个监听器 upload.setProgressListener(pListener);(匿名内部类)
package upload;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
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.FileUploadException;
import org.apache.commons.fileupload.ProgressListener;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
@SuppressWarnings ( "serial" )
public class UploadServlet extends HttpServlet {
@SuppressWarnings ( "unchecked" )
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
DiskFileItemFactory factory = new DiskFileItemFactory();
factory.setRepository(new File( "d:/temp" ));
ServletFileUpload upload = new ServletFileUpload(factory);
if (ServletFileUpload.isMultipartContent(request)) {
try {
upload.setProgressListener(new ProgressListener() {
public void update( long bytesRead, long contentLength,
int items) {
System.out.println("文件大小:" + contentLength + "b" );
System.out.println("已上传:" + bytesRead + "b" );
}
});
List list = upload.parseRequest(request);
for (FileItem item : list) {
if (item.isFormField()) {
System.out.println("普通字段:" + item.getString());
} else {
InputStream in = item.getInputStream();
String fileName = item.getName();
String path = makeDir(getServletContext().getRealPath(
"/WEB-INF/upload" ), fileName);
FileOutputStream fos = new FileOutputStream(path);
int len = 0 ;
byte [] b = new byte [ 1024 ];
while ((len = in.read(b)) != - 1 ) {
fos.write(b, 0 , len);
}
fos.close();
}
}
} catch (FileUploadException e) {
request.setAttribute("message" , "上传失败。。" );
request.getRequestDispatcher("/message" ).forward(request,
response);
}
}
}
public String makeDir(String serverDir, String fileName) {
int hashCode = fileName.hashCode();
int dir_1 = hashCode & 0xf ;
int dir_2 = (hashCode >> 4 ) & 0xf ;
String path = serverDir + File.separator + dir_1 + File.separator
+ dir_2;
File dir = new File(path);
if (!dir.exists()) {
dir.mkdirs();
}
return path + File.separator + fileName;
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
8、实现动态添加删除上传输入项
< %@ page language = "java" import = "java.util.*" pageEncoding = "UTF-8" % >
>
< html >
< head >
< title > title >
< script type = "text/javascript" >
function addInput(){
var div = document .createElement("div");
var fileInput = document .createElement("input");
fileInput.type = "file" ;
fileInput.name = "addFile" ;
div.appendChild(fileInput);
var delInput = document .createElement("input");
delInput.type = "button" ;
delInput.value = "删除" ;
delInput.onclick = function del(){
this.parentNode.parentNode.removeChild(this.parentNode);
};
div.appendChild(delInput);
document.getElementById("files").appendChild(div);
}
script >
head >
< body >
< form action = "${pageContext.request.contextPath }/servlet/UploadServlet" method = "post" enctype = "multipart/form-data" >
上传用户:< input type = "text" name = "username" > < br >
< input type = "file" name = "f" > < br >
< div id = "files" >
div >
< input type = "button" value = "添加上传文件" onclick = "addInput()" > < br >
< input type = "submit" value = "上传" > < br >
form >
body >
html >
二、文件下载
package download;
import java.io.File;
import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class ShowList extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
File file = new File(getServletContext().getRealPath( "/WEB-INF/upload" ));
Map map = new LinkedHashMap();
map = getResource(file, map);
request.setAttribute("map" , map);
request.getRequestDispatcher("/showlist.jsp" ).forward(request, response);
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
public Map getResource(File file,Map map){
if (!file.isDirectory()){
String uuidname = file.getName();
String realname = uuidname.substring(uuidname.indexOf("_" )+ 1 );
map.put(uuidname, realname);
}else {
for (File child : file.listFiles()) {
getResource(child, map);
}
}
return map;
}
}
package download;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URLEncoder;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class DownLoadServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String path = new String(request.getParameter( "id" ).getBytes( "iso-8859-1" ), "utf-8" );
String realname = path.substring(path.lastIndexOf("\\" ) + 1 );
File file = new File(getFilepath(getServletContext().getRealPath(
"/WEB-INF/upload" ), realname));
realname = file.getName().substring(file.getName().indexOf("_" ) + 1 );
response.setHeader("content-disposition" , "attachment;filename=" + URLEncoder.encode(realname, "utf-8" ));
ServletOutputStream outputStream = response.getOutputStream();
FileInputStream fis = new FileInputStream(file);
int len = 0 ;
byte [] b = new byte [ 1024 ];
while ((len = fis.read(b)) != - 1 ) {
outputStream.write(b, 0 , len);
}
fis.close();
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
public String getFilepath(String serverPath, String filepath) {
int hashCode = filepath.hashCode();
int dir_1 = hashCode & 0xf ;
int dir_2 = (hashCode >> 4 ) & 0xf ;
String path = serverPath + File.separator + dir_1 + File.separator
+ dir_2 + File.separator + filepath;
return path;
}
}
< %@ page language = "java" import = "java.util.*" pageEncoding = "utf-8" % >
< %@taglib uri = "http://java.sun.com/jsp/jstl/core" prefix = "c" % >
>
< html >
< head >
< title > My JSP 'index.jsp' starting page title >
head >
< body >
< c:forEach var = "file" items = "${requestScope.map}" >
< c:url var = "path" value = "/servlet/DownLoadServlet" >
< c:param name = "id" value = "${file.key }" />
c:url >
${file.value } < a href = "${path }" > 下载 a > < br >
c:forEach >
body >
html >
转载来自:http://blog.csdn.net/driverking/article/details/6750802