文件上传与下载

来源:互联网 发布:店铺logo设计软件 编辑:程序博客网 时间:2024/06/14 08:23

一文件上传


1文件上传对页面的要求

        1.1必须是表单,不能是超链接;

        1.2表单的提交方法必须是post,不能是get;

        1.3表单的enctype必须是multipart/form-data类型;

        1.4在表单中添加<input type="file" name="">


2对比文件上传表单和普通文本表单的区别


2.1对普通文本表单的测试

jsp代码:
<%@ 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>普通表单</title></head><body><form action="${pageContext.request.contextPath }/AServlet" method="post""><input type="text" name="username"><br/><input type="file" name="file1"><br/><input type="file" name="file2"><br/><input type="submit" value="提交"></form></body></html>
浏览器输入内容:

查看提交后的内容


        查看表单的请求正文,我们发现请求中只有文件名称,而没有文件内容。也就是说,当表单的enctype不是multipart/form-data时,请求中是不包含文件内容,而只有文件名称,这说明普通文本表单中的<input type="file">和<input type="text">没什么区别。


2.2对文件上传表单的测试

jsp代码:
<%@ 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>普通表单</title></head><body><form action="${pageContext.request.contextPath }/AServlet" method="post"" enctype="multipart/form-data"><input type="text" name="username"><br/><input type="file" name="file1"><br/><input type="file" name="file2"><br/><input type="submit" value="提交"></form></body></html>
浏览器输入内容

查看提交后的内容

        查看表单的请求数据正文部分,发现正文部分是由多个部件组成,每个部件对应一个表单字段,每个部件都有自己的头信息。头信息下面是空行,空行下面是字段的正文部分。多个部件之间使用随机生成的分隔线隔开。
        文本字段的头信息中只包含一条头信息,即Content-Disposition,这个头信息的值有两个部分,第一部分是固定的,即form-data,第二部分为字段的名称。在空行后面就是正文部分了,正文部分就是在文本框中填写的内容。
        文件字段的头信息中包含两条头信息,Content-Disposition和Content-Type。Content-Disposition中多出一个filename,它指定的是上传的文件名称。而Content-Type指定的是上传文件的类型。文件字段的正文部分就是文件的内容。


3文件上传对Servlet的要求

        当提交的表单是文件上传表单时,这时,对Servlet也是有一定要求的。但我们必须明确的一点是,就算是文件上传表单,其上传数据也是被封装在了request中。

        request.getParameter(String name)获得是的表单中指定字段的字符内容,但文件上传表单也不再是字符内容,而是字节内容,所以这个方法已经失效。这时可以使用request的getInputStream方法得到ServletInputStream对象,它是InputStream的子类对象,这个ServletInputStream对象对应整个表单的正文内容,真好是我们需要解析的流内容。当然解析它比较麻烦,所以通常情况下,我们可以一个工具类,Apache下的commons-fileupload;


4commoms-fileupload组件的简单介绍


4.1fileupload概述

        fileupload组件是Apache下commons组件下的上传组件,主要帮我们解析request.getInputStream();

        fileupload组件需要的jar包有:

                commons-fileupload.jar,核心包;

                commons-io.jar,依赖包。


4.2fileupload简单应用

        fileupload的核心类有:DiskFileItemFactory、ServletFileUpload、FileItem。

        使用fileupload组件的步骤如下:

                1.创建工厂类DiskFileItemFactory对象:DiskFileItemFactory factory = new DiskFileItemFactory()

                2.使用工厂创建解析器对象:ServletFileUpload fileUpload = new ServletFileUpload(factory)

                3.使用解析器来解析request对象:List<FileItem> list = fileUpload.parseRequest(request)

        主要介绍下FileItem对象,因为它才是我们最终要的结果。一个FileItem对象对应一个表单项(表单字段)。一个表单中存在文件字段和普通字段,可以使用FileItem类的isFormField()方法来判断表单字段是否为普通字段,如果不是普通字段,那么就是文件字段了。

        还有一些的方法如下:

                String getName()

                方法介绍:获取文件字段的文件名称;

                String getString()

                方法介绍:获取字段的内容,如果是文件字段,那么获取的是文件内容,当然上传的内容必须是文本文件;

                String getFieldName()

                方法介绍:获取字段名字,注意和上面提到的getName方法进行区别;

                String getContentType()

                方法介绍:获取上传文件的类型;

                int getSize();

                方法介绍:获取上传文件的大小;

                boolean isFormField()

                方法介绍:判断当店表单字段是否为普通文本字段,如果返回false,说明是文件字段;

                InputStream getInputStrean()

                方法介绍:获取上传文件对应的输入流;

                void write(File file)

                方法介绍:把上传的文件保存到指定的文件中;


5文件上传的细节


51把上传的文件保存到web-inf目录下

        如果没有把上传的文件保存到web-inf目录下,那么是相当危险的操作,因为如果有人恶意上传文件,比如说是一个jsp,然后运行这个jsp,鬼知道他在jsp文件里干了什么?想想是不是觉得很可怕???


5.2文件名称问题

        有些浏览器,比如说是IE6,在得到文件名称的时候,可能是个带有前缀的绝对地址,这个时候我们就要自己通过截取字符串的方式来得到文件名;


5.3文件名中文乱码问题

        当上传的文件的文件名包含中文的时候,就要自己设置编码了,commons-fileupload给我们提供了两种设置编码的方法:

        request.setCharacterEncoding(String):这种方式是我们最为熟悉的方式了;

        fileUpload.setHeaderEncdoing(String):这种方式的优先级高于前一种。


5.4上传文件同名问题

        在实际过程中,我们绝对有可能遇到文件同名的问题,这个时候我们就可以重新给上传的问题进行命名,比如说UUID的方式;


5.5一个目录不能存放过多的文件

        如果一个目录存放了过多的文件,就会出现打开速度变慢的问题,所以我们可以考虑在多个目录中存放上传的文件,这也称之为目录打散。


5.6上传的单个文件的大小限制

        可以通过编码的方式来设置文件上传时当个文件的大小,ServletFileUpload类的setFileSizeMax(ling)就可以了。参数就是文件上传的上限字节数,例如:servletFileUpload.setFileSizeMax(1024*20)表示上限为10kb。一旦上传的文件超多了上限,那么就会抛出FileUploadBase.fileSizeLimitExceededException异常。我们就可以通过捕获这个异常,从而做下一步的操作。


5.7上传文件的总大小限制

        有时我们需要限制一个请求的大小,也就是说个请求的最大字节数(所有表单项之和)。实现这一功能也非常简单,只需要调用ServletFileUpload类的setSizeMax(long)方法,例如:servletFileUpload.setSizeMax(1024*30),则说明真个表单的最大字节数是30kb,如果超出限制,Servlet类的parseRequest()方法会抛出FileUploadBase.SizeLimitExceededException异常。


5.8缓存大小与临时目录

        大家想一想,如果我们在上传一个很大的文件时,比如说达到了几个G,先把电影保存到内存中,然后再通过内存copy到服务器的硬盘上,那你的内存能吃的消吗?所以fileupload不可能把文件都保存在内存中,fileupload会判断文件大小是否超出了10kb,如果是那么就把文件保存到硬盘上,如果没有超出,那么就保存在内存中。10KB是fileupload默认的值,我们可以来设置它。当文件保存到硬盘时,fileupload是把文件保存到系统临时目录,当然你也可以去设置临时目录。

代码示例:

jsp代码:

<%@ 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>上传</title></head><body>${msg }<form action="${pageContext.request.contextPath }/UploadServlet" method="post" enctype="multipart/form-data"><input type="text" name="filaName"><br/><input type="file" name="file"><br/><input type="submit" value="提交"></form></body></html>
Servlet代码:

package cn.ccnu.upload;import java.io.File;import java.io.IOException;import java.util.List;import java.util.UUID;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.FileUpload;import org.apache.commons.fileupload.FileUploadBase;import org.apache.commons.fileupload.FileUploadException;import org.apache.commons.fileupload.disk.DiskFileItemFactory;import org.apache.commons.fileupload.servlet.ServletFileUpload;public class UploadServlet extends HttpServlet {public void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {System.out.println("文件上传!!!");request.setCharacterEncoding("UTF-8");response.setContentType("text/html;charset=UTF-8");/* * 文件上传三步曲: * 1,工厂 * 2,解析器 * 3,表单项 *  *///获得文件上传的工厂,同时设置缓存大小为20kb,如果超出20kb就会保存到临时目录//DiskFileItemFactory factory = new DiskFileItemFactory(1024*20, new File("F://temp"));//不设置缓存大小的话,默认为10kb,临时目录为系统默认目录DiskFileItemFactory factory = new DiskFileItemFactory();//获得解析器ServletFileUpload sfu = new ServletFileUpload(factory);//设置单个文件的字节上限//sfu.setFileSizeMax(1024 * 10);//设置整个表单的文件字节上限//sfu.setSizeMax(1024 * 50);//注意上面的两句要在调用parseRequest()之前try {//获得所有的文件表单项List<FileItem> fileItemList = sfu.parseRequest(request);//对所获得的文件表单项进行循环for (FileItem fileItem : fileItemList) {//如果不是普通表单项,即是文件表单项if(!fileItem.isFormField()){//得到上传文件的文件名String fileName = fileItem.getName();//判断文件名是否为空,如果为空,则开始下一次循环if(fileName == null || fileName.isEmpty()){continue;}//因为有些浏览器在上传文件时,会得到文件的绝对路径,所以这个时候//要通过截取得到文件的文件名int index = fileName.indexOf("\\");if(index != -1){fileName = fileName.substring(index + 1);}//为了防止上传的文件出现文件名重复的问题,应该重新命名文件fileName = UUID.randomUUID().toString().replace("-", "").toLowerCase() + "-" + fileName;/* * 文件的保存路径虽然没有规定一定保存在哪里,但最好是保存在web-inf目录下, * 因为这个目录不能直接供外界访问 *///保存路径String root = this.getServletContext().getRealPath("WEB-INF/files");//打散目录/* * 打散目录,为什么要打散目录? * 因为如果只使用一个目录的话,会随着所在目录文件越来越多,导致读取速度变慢 * 这里使用的打散目录的方法为:取文件名的hashCode,然后转化成16进制的字符 * 取前面两位字符,形成二级目录 *///得到文件名的hashCodeint hCode = fileName.hashCode();//转换成16进制String hex = Integer.toHexString(hCode);//取hex的前两个字母,与root一起组成一个完整的目录File savePath = new File(root, hex.charAt(0) + "/" + hex.charAt(1));//创建目录链savePath.mkdirs();//创建目录文件File file = new File(savePath, fileName);//保存fileItem.write(file);}}} catch (Exception e) {if(e instanceof FileUploadBase.FileSizeLimitExceededException){request.setAttribute("msg", "上传文件超过10kb");request.getRequestDispatcher("/form/upload.jsp").forward(request, response);return;}throw new RuntimeException();}}public void doPost(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {doGet(request, response);}}


二文件下载


1什么是下载

        下载就是向客户端响应字节数据!原来我们响应的都是html的字符数据!而文件下载,响应的是字节数据,即把一个文件变成字节数组,使用response.getOutputStream()来响应给浏览器!!!


2下载的要点

        简单说就是:两个头一个流!

        两个头

                Content-Type:你传递给客户端的文件是什么MIME类型,例如:image/pjpeg;可以直接设置MIME类型,也可以通过文件名称调用ServletContext的getMimeType()方法,得到MIME类型!

                Content-Disposition:它的默认值为inline,表示在浏览器窗口中打开!现在将其设置为attachment,表示不在浏览器中打开,即下载的方式。后面的filename表示显示在下载框中的文件名称。其形式为:attachment;filename=xxx

        一个流
                流:要下载的文件数据!

代码示例:

package cn.ccnu.download;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;import sun.misc.BASE64Encoder;public class DownloadServlet extends HttpServlet {public void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {/* * 两个头一个流 * 两个头: * 1. Content-Type,传递给客服端的数据是什么MIME类型 *  2. Content-Disposition,默认是inline表示在浏览器中打开,如果是下载的话 *  应该被设置成attachment * 流,就是数据输出流 *  */String fileName = "C:\\Users\\Jason\\Desktop\\常用资料\\按键.jpg";//防止显示的中文乱码,其中的一种解决办法String framename = new String("按键".getBytes("UTF-8"), "ISO-8859-1");//获得文件的MIME类型,这里使用的是根据文件名称来获得String contentType = this.getServletContext().getMimeType(fileName);//设置以下载的方法打开文件String contentDisposition = "attachment;filename=" + framename;//设置两个头response.setHeader("Content-Type", contentType);response.setHeader("Content-Disposition", contentDisposition);//得到一个输入流FileInputStream fin = new FileInputStream(fileName);//获取绑定了响应段的输出流ServletOutputStream out = response.getOutputStream();//创建缓冲区byte[] buffer = new byte[1024];int length = 0;//循环将输入流中的数据读到缓冲区中while((length = fin.read(buffer)) > 0){out.write(buffer, 0, length);}//关闭两个流fin.close();out.close();}// 用来对下载的文件名称进行编码的!//因为浏览器的不同,有些浏览器采用base64编码,有些采用URL编码//这是一种比较通用的解决办法public static String filenameEncoding(String filename, HttpServletRequest request) throws IOException {String agent = request.getHeader("User-Agent"); //获取浏览器if (agent.contains("Firefox")) {BASE64Encoder base64Encoder = new BASE64Encoder();filename = "=?utf-8?B?"+ base64Encoder.encode(filename.getBytes("utf-8"))+ "?=";} else if(agent.contains("MSIE")) {filename = URLEncoder.encode(filename, "utf-8");} else {filename = URLEncoder.encode(filename, "utf-8");}return filename;}public void doPost(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {doGet(request, response);}}


3下载的细节

        在下载的过程中需要注意的细节就是显示的文件名的中文乱码问题,因为不同的浏览器采用不同的编码方式,比如说FireFox采用Base64编码,而对于其它大部分浏览都采用的是URL编码的方式。

比较通用的解决办法

1:String newFileName = new Stirng(oldName.getBytes("UTF-8"), "IOS-8859-1");

2:

public static String filenameEncoding(String filename, HttpServletRequest request) throws IOException {String agent = request.getHeader("User-Agent"); //获取浏览器if (agent.contains("Firefox")) {BASE64Encoder base64Encoder = new BASE64Encoder();filename = "=?utf-8?B?"+ base64Encoder.encode(filename.getBytes("utf-8"))+ "?=";} else if(agent.contains("MSIE")) {filename = URLEncoder.encode(filename, "utf-8");} else {filename = URLEncoder.encode(filename, "utf-8");}return filename;}



0 0
原创粉丝点击