欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

文件的上传

程序员文章站 2022-03-11 21:48:54
...

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;
	}
}    
相关标签: 文件上传