JavaWeb 文件上传与下载

一、文件上传

       实现 Web 开发中的文件上传功能,两个操作:在 Web 页面添加上传输入项,在 Servlet 中读取上传文件的数据并保存在本地硬盘中。

     1、Web 端上传文件。在 Web 页面中添加上传输入项:<input type="file"> 设置文件上传输入项时应注意:(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<FileItem> 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<FileItem> 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"%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<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.οnclick=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="添加上传文件" οnclick="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<String, String> map = new LinkedHashMap<String, String>();
		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<String, String> getResource(File file,Map<String, String> 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" %>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<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>


  • 5
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值