文件上传及几个细节问题

搭建网站时,有一个常用的功能就是文件的上传与下载。在添加一个文件上传功能时,主要有两个步骤:

1、创建一个web页面,填写需要上传文件的相关输入项

1.1通过<input type="file" />标签可以添加一个上传文件,需要注意的是,input标签一定要设置name属性,否则浏览器不会发送上传的文件数据。

1.2另外还需要把form标签的enctype属性设置成multipart/form-data,设置该值后,浏览器将会把文件数据附带在http请求消息体中,并使用MIME协议对上传文件进行描述,以方便接受方对上传文件进行解析和处理

2、创建一个servlet,负责解析上传过来的文件,读取并将其保存至硬盘中

2.1 request提供了一个getInputStream方法,可以获取到客户端提交过来的数据,但由于客户可能同时上传多个文件,在servelt上直接编程将会很麻烦。

2.2 apache组织提供一个用来处理表单文件上传的组件,该组件提供了性能优异的API,方便开发者实现表单文件上传功能。此组件名为:Commons-fileupload

3、使用Commons-fileupload组件,需要导入相关开发jar包,此jar包名为:

Commons-fileuploadcommons-io, commons-io不属于文件上传组件的开发jar包,但Commons-fileupload从1.1版本后,需要其支持。


使用此开源组件,开发流程图如下:

4、Servlet核心开发步骤

4.1 创建一个DiskFileItemFactory工厂

DiskFileItemFactory factory = new DiskFileItemFactory();

4.2 创建一个ServletFileUpload对象,此对象将封装对上传文件数据的处理操作

ServletFileUpload upload = new ServletFileUpload(factory);

4.3 通过ServletFileUpload的isMultipartContent方法判断是否是表单文件上传,否则按照正常方式处理

if (!upload.isMultipartContent(request)) {
				// 安传统方式读取数据,即表单数据上传的数据 enctype="multipart/form-data"
				return;
			}
4.4 通过ServletFileUpload的parseRequest方法获取request提交的每项表单数据,每项表单数据将被封装在一个FileItem对象中

List<FileItem> list;
			list = (List<FileItem>) upload.parseRequest(request);
4.5 处理每项表单数据,根据FileItem的isFormField方法来区分提交的数据是否是普通表单数据还是文件上传数据,若是文件上传数据,调用FileItem的getInputStream方法可以获取到上传文件的数据。

整体代码示例如下:

public class FileUploadServlet extends HttpServlet {

	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {

		// request是安装MIME协议传输,不能再以request.getParameter方式读取
		String savePath = this.getServletContext().getRealPath(
				"/WEB-INF/upload");
		
		//1、解决表单名和上传文件名中文乱码问题
		//	request.setCharacterEncoding("UTF-8");
		try {
			DiskFileItemFactory factory = new DiskFileItemFactory();
			ServletFileUpload upload = new ServletFileUpload(factory);
			
			//方法二:解决上传文件名中文乱码问题
			upload.setHeaderEncoding("UTF-8");

			if (!upload.isMultipartContent(request)) {
				// 安传统方式读取数据,即表单数据上传的数据 enctype="multipart/form-data"
				return;
			}

			//上传的数据时按照enctype="multipart/form-data"方式传上来的,其数据时按照MIME协议上传的
			List<FileItem> list;
			list = (List<FileItem>) upload.parseRequest(request);

			for (FileItem item : list) {
				if (item.isFormField()) {
					String name = item.getFieldName();
					//方法一:数据表单内容的中文乱码问题,按照multipart/form-data上传的数据,都按照iso8859-1进行编码
					//	要解决此中文乱码问题,需要先对获取到的数据进行iso8859-1进行解码,在按照UTF-8进行编码
				//	String value = item.getString();
				//	value = new String(value.getBytes("iso8859-1"), "UTF-8");
					
					//方法二:解决数据表单内容的中文乱码问题,采用FiltItem类中的getString(Charset)方法,此方法重载了getString
					//获取到的数据将按照指定的编码方式进行编码
					String value = item.getString("utf-8");
					
					System.out.println(name + ":" + value);
				} else {
					String fileName = item.getName();
					// 不同浏览器通过request上传的文件名不一致,有是完整路径,有点是文件名: c:\a\b\1.txt or
					// 1.txt
					fileName = fileName
							.substring((fileName.lastIndexOf("\\") + 1));
					InputStream in = item.getInputStream();
					
					//FileOutputStream out = new FileOutputStream(savePath + "\\"
					//	+ fileName);
					//解决上传文件重名问题
					String saveName = makeFileName(fileName);
			//		FileOutputStream out = new FileOutputStream(savePath + "\\"
			//				+saveName);
					
					//文件打散算法
					String realSavePath = makeSavePath(savePath, fileName);
					FileOutputStream out = new FileOutputStream(realSavePath + "\\"
							+saveName);
					
					byte[] buf = new byte[1024];
					int len = 0;
					while ((len = in.read(buf)) > 0) {
						out.write(buf);
					}

					out.close();
					in.close();
				}
			}
		} catch (FileUploadException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}

	public void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {

		doGet(request, response);
	}
	
	//解决上传文件重名导致文件覆盖问题
	private String makeFileName(String fileName){
		return UUID.randomUUID()+"_"+fileName;
	}
	
	//文件打散问题,解决一个文件下文件过多的问题
	private String makeSavePath(String savePath, String fileName){
		if(fileName==null || savePath==null){
			return null;
		}
		
		int hCode = fileName.hashCode();
		int dir1 = hCode&0x0f;
		int dir2 = (hCode&0xf0)>>4;
		
		String dir = savePath+"\\"+dir1+"\\"+dir2;
		
		//文件要是不存在,创建此文件
		File f = new File(dir);
		if(!f.exists()){
			f.mkdirs();
		}
		
		return dir;
	}

}

5 上面的示例代码还解决了几个文件上传中的细节问题

5.1 表单名和上传文件名中文乱码问题

解决此问题有两种方法

一:通过request.setCharacterEncoding("UTF-8")来设置中文编码

二:通过ServletFileUpload的setHeaderEncoding方法来设置中文编码

ServletFileUpload upload = new ServletFileUpload(factory);
			
			//方法二:解决上传文件名中文乱码问题
			upload.setHeaderEncoding("UTF-8");

5.2 提交的表单内容中文乱码问题

由于设置了form标签的enctype属性值为multipart/form-data,其将通过MIME协议向服务器提交数据,所有表单数据都是通过iso8859-1来编码的。解决此中文乱码问题有两种方法

一:将表单数据通过iso8859-1进行解码,得到源字节码,在通过UTF-8编码

if (item.isFormField()) {
					String name = item.getFieldName();
					//方法一:数据表单内容的中文乱码问题,按照multipart/form-data上传的数据,都按照iso8859-1进行编码
					//	要解决此中文乱码问题,需要先对获取到的数据进行iso8859-1进行解码,在按照UTF-8进行编码
				//	String value = item.getString();
				//	value = new String(value.getBytes("iso8859-1"), "UTF-8");
					
					//方法二:解决数据表单内容的中文乱码问题,采用FiltItem类中的getString(Charset)方法,此方法重载了getString
					//获取到的数据将按照指定的编码方式进行编码
					String value = item.getString("utf-8");
					
					System.out.println(name + ":" + value);

二:通过调用FileItem的getString(Charset charset)来获取表单内容。

5.3 上传文件重名问题

使用UUID方法重写文件名

//解决上传文件重名导致文件覆盖问题
	private String makeFileName(String fileName){
		return UUID.randomUUID()+"_"+fileName;
	}

5.4 上传文件打散问题

使用文件名在内存中的地址(hashcode)来创建存储路径,总共两级目录,第一级目录名为filename 哈希值最低0-4位值,第二级目录名为哈希值低5-8位。

private String makeSavePath(String savePath, String fileName){
		if(fileName==null || savePath==null){
			return null;
		}
		
		int hCode = fileName.hashCode();
		int dir1 = hCode&0x0f;
		int dir2 = (hCode&0xf0)>>4;
		
		String dir = savePath+"\\"+dir1+"\\"+dir2;
		
		//文件要是不存在,创建此文件
		File f = new File(dir);
		if(!f.exists()){
			f.mkdirs();
		}
		
		return dir;
	}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值