26.文件上传与下载

发布时间:2023年12月21日

基于表单的文件上传

? 如果在表单中使用表单元素<input type=“file” />,浏览器在解析表单时,会自动生成一个输入框和一个按钮,输入框可供用户填写本地文件的文件名和路径名,按钮可以让浏览器打开一个文件选择框供用户选择文件:
在这里插入图片描述在这里插入图片描述

Enctype 属性

  • 当表单需要上传文件时,需指定表单 enctype 的值为 multipart/form-data;
  • 在 form 元素的语法中,enctype 属性指定将数据发送到服务器时浏览器使用的编码类型。
  • enctype 属性取值:
    • –application/x-www-form-urlencoded:表单 enctype 属性的默认值,这种编码方案使用有限的字符集。
    • –multipart/form-data:form 设定了enctype=“multipart/form-data”属性后,表示表单以二进制传输数据。
Commons-fileupload 组件
  • Commons-fileupload 组件是 Apache 开源代码组织用来处理表单文件上传的一个子项目,该组件性能优异,可以支持任意大小的文件的上传
  • Commons-fileupload 组件从 1.1 版本开始依赖 Apache 的另一个项目:commons-io
Commons-fileupload 组件上传的基本原理
  • FileUpload组件将页面提交的所有元素(普通form表单域,如text和文件域file)都看作一样的FileItem,这样上传页面提交的 request请求也就是一个FileItem的有序组合;
  • FileUpload组件可以解析该request,并返回一个一个的FileItem;
  • 对每一个FileItem,FileUpload组件可以判断出它是普通form表单域还是文件file域,从而根据不同的类型,采取不同的操作;
  • 如果是表单域,就读出其值,如果是文件域,就保存文件到服务器硬盘上或者内存中。
Commons-fileupload 组件API
  • 在 Commons-fileupload 组件中,主要用到以下三个接口和类:
    在这里插入图片描述

  • ServletFileUpload 负责处理上传的文件数据,并将每部分的数据封装成一到 FileItem 对象中。

  • DiskFileItemFactory 是创建 FileItem 对象的工厂,在这个工厂类中可以配置内存缓冲区大小和存放临时文件的目录。

  • ServletFileUpload 在接收上传文件数据时,会将内容保存到内存缓存区中,如果文件内容超过了 DiskFileItemFactory 指定的缓冲区的大小,那么文件将被保存到磁盘上,存储为 DiskFileItemFactory 指定目录中的临时文件。等文件数据都接收完毕后,ServletUpload 在从文件中将数据写入到上传文件目录下的文件中。

核心API—DiskFileItemFactory

DiskFileItemFactory 是创建FileItem 对象的工厂,这个工厂类常用方法:

  • 1、public void setSizeThreshold(int sizeThreshold) :设置内存缓冲区的大小,默认值为10K。当上传文件大于缓冲区大小时,fileupload组件将使用临时文件缓存上传文件。
  • 2、public void setRepository(Java.io.File repository) :指定临时文件目录,默认值为System.getProperty(“java.io.tmpdir”).
  • 3、public DiskFileItemFactory(int sizeThreshold,java.io.File repository) :构造函数
核心API—-ServletFileUpload

ServletFileUpload 负责处理上传的文件数据,并将表单中每个输入项封装成一个FileItem 对象中。常用方法有:

  • 1、boolean isMultipartContent(HttpServletRequest request) :判断上传表单是否为multipart/form-data类型
  • 2、List parseRequest(HttpServletRequest request):解析request对象,并把表单中的每一个输入项包装成一个fileItem 对象,并返回一个保存了所有FileItem的list集合。
  • 3、setFileSizeMax(long fileSizeMax) :设置上传文件的最大值(单个文件),用于设置单个上传文件的最大尺寸限制,以防止客户端恶意上传超大文件来浪费服务器端的存储空间。其参数是以字节为单位的long型数字。
  • 4、setSizeMax(long sizeMax) :设置上传文件总量的最大值(所有上传文件),用于设置请求消息实体内容(即所有上传数据)的最大尺寸限制,以防止客户端恶意上传超大文件来浪费服务器端的存储空间。其参数是以字节为单位的long型数字。
  • 5、setHeaderEncoding(java.lang.String encoding) :设置编码格式。在文件上传请求的消息体中,除了普通表单域的值是文本内容以外,文件上传字段中的文件路径名也是文本,在内存中保存的是它们的某种字符集编码的字节数组,Apache文件上传组件在读取这些内容时,必须知道它们所采用的字符集编码,才能将它们转换成正确的字符文本返回。
核心API—FileItem
  • 1、boolean isFormField(): isFormField方法用于判断FileItem类对象封装的数据是一个普通文本表单字段,还是一个文件表单字段,如果是普通表单字段则返回true,否则返回false。
  • 2、 String getName()用于获得文件上传字段中的文件名。注意IE或FireFox中获取的文件名是不一样的,IE中是绝对路径,FireFox中只是文件名。
  • 3、String getFieldName()用于返回表单标签name属性的值。
  • 4、 void write(File file):用于将FileItem对象中保存的主体内容保存到某个指定的文件中。如果FileItem对象中的主体内容是保存在某个临时文件中,该方法顺利完成后,临时文件有可能会被清除。该方法也可将普通表单字段内容写入到一个文件中,但它主要用途是将上传的文件内容保存在本地文件系统中。
  • 5、 String getString():用于将FileItem对象中保存的数据流内容以一个字符串返回,它有两个重载的定义形式:
    public Java.lang.String getString();
    public java.lang.String getString(java.lang.String encoding) throws java.io.UnsupportedEncodingException
    前者使用缺省的字符集编码将主体内容转换成字符串,后者使用参数指定的字符集编码将主体内容转换成字符串。如果在读取普通表单字段元素的内容时出现了中文乱码现象,请调用第二个getString方法,并为之传递正确的字符集编码名称。
  • 6、 void delete():delete方法用来清空FileItem类对象中存放的主体内容,如果主体内容被保存在临时文件中,delete方法将删除该临时文件。尽管当FileItem对象被垃圾收集器收集时会自动清除临时文件,但及时调用delete方法可以更早的清除临时文件,释放系统存储资源。
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1、创建DiskFileItemFactory对象,设置缓冲区大小和临时文件目录。
        DiskFileItemFactory factory = new DiskFileItemFactory();
        factory.setSizeThreshold(1024 * 500);
        File tempDirectory = new File("d:\\tempDirectory");
        factory.setRepository(tempDirectory);
//2、使用DiskFileItemFactory 对象创建ServletFileUpload对象,并设置上传文件的大小限制。
        ServletFileUpload upload = new ServletFileUpload(factory);
        upload.setSizeMax(1024 * 1024 * 5);
        try {
//3、调用ServletFileUpload.parseRequest方法解析request对象,得到一个保存了所有上传内容的List对象。
            List<FileItem> items = upload.parseRequest(req);
//4、对list进行迭代,每迭代一个FileItem对象,调用其isFormField方法判断是否是上传文件:
            for (FileItem item : items) {
//4.1、 为普通表单字段,则调用getFieldName、getString方法得到字段名和字段值。
                if (item.isFormField()) {
                    String name = item.getFieldName();
                    String value = item.getString();
                    System.out.println(name + ": " + value);
                }
//4.2、为上传文件,则调用getInputStream方法得到数据输入流,从而读取上传数据。
                else {
                    String fieldName = item.getFieldName();
                    String fileName = item.getName();
                    String contentType = item.getContentType();
                    long sizeInBytes = item.getSize();
                    System.out.println(fieldName);
                    System.out.println(fileName);
                    System.out.println(contentType);
                    System.out.println(sizeInBytes);
                    InputStream in = item.getInputStream();
                    byte[] buffer = new byte[1024];
                    int len = 0;
                    File file = new File("d://files");
                    if (!file.exists()){
                        file.mkdir();
                    }
                    fileName = "d:\\files\\" + fileName;
                    System.out.println(fileName);
                    OutputStream out = new FileOutputStream(fileName);
                    while ((len = in.read(buffer)) != -1) {
                        out.write(buffer, 0, len);
                    }
                    out.close();
                    in.close();
                }
            }
        } catch (FileUploadException e) {
            e.printStackTrace();
        }
    }

文件的下载

  • 下载文件的关键是几点:
    1. 服务器以一个流的形式将文件发送给浏览器。
    2. 发送流的同时还需要设置几个响应头,来告诉浏览器下载的信息。
    • 具体响应头如下:
      • Content-Type
        • 下载文件的MIME类型
        • 可以通过 getMimeType(String file)获取
        • 也可以直接手动指定
        • 使用setContentType(String type);
        • 响应头样式:
          • Content-Type: audio/mpeg
        • Content-Disposition
          • 下载文件的名字,主要作用是提供一个默认的用户名
          • 通过setHeader(“Content-Disposition”, disposition)设置
          • 响应头样式:
            • Content-Disposition: attachment; filename=xxx.mp3
          • Content-Length
            • 下载文件的长度,用于设置文件的长处(不必须)
            • 通过 setContentLength(int len)设置。
            • 设置后样式:
              • Content-Length: 3140995
  1. 接下来需要以输入流的形式读入硬盘上的文件
    • FileInputStream is = new FileInputStream(file);
    • 这个流就是我们一会要发送给浏览器的内容
  2. 通过response获取一个输出流,并将文件(输入流)通过该流发送给浏览器
    • 获取输出流
      • ServletOutputStream out = response.getOutputStream();通过输出流向浏览器发送文件(不要忘了关闭输入流)
byte[] b = new byte[1024];
int len = 0;
while((len=is.read(b))> 0){
	out.write(b, 0, len);
}
is.close();

步骤:

  • 获取文件的流:
    String realPath = getServletContext().getRealPath(“/WEB-INF/mp3/中国话.mp3”);
    //获取文件的File对象
    File file = new File(realPath);
    //获取文件的输入流
    FileInputStream is = new FileInputStream(file);
  • 获取头信息
    //获取文件的MIME信息
    String contentType = getServletContext().getMimeType(realPath);
    //设置下载文件的名字
    String filename = “zhongguohua.mp3”;
    //创建Content-Disposition信息
    String disposition = “attachment; filename=”+ filename ;
    //获取文件长度
    long size = file.length()
  • 设置头信息
    //设置Content-Type
    response.setContentType(contentType);
    //设置Content-Disposition
    response.setHeader(“Content-Disposition”, disposition);
    //设置文件长度
    response.setContentLength((int)size)
  • 发送文件
    //通过response获取输出流,用于向浏览器输出内容
    ServletOutputStream out = response.getOutputStream();
    //将文件输入流通过输出流输出
    byte[] b = new byte[1024];
    int len = 0;
    while((len=is.read(b))> 0){
    out.write(b, 0, len);
    }
    //最后不要忘记关闭输入流,输出流由Tomcat自己处理,我们不用手动关闭
    is.close()
乱码:

至此实际上文件下载的主要功能都已经完成。但是还有一个问题我们这里没有体现出来,因为目前我们的文件名使用的是纯英文的,没有乱码问题。这里如果我们要使用中文文件名的话,毫无疑问会出现乱码问题。
解决此问题的方法很简单,在获取文件名之后为文件名进行编码:
fileName = java.net.URLEncoder.encode(filename,"utf-8");
或者先用GBK解码,再使用iso8859-1编码
fileName = new String(name.getBytes("gbk"),"iso8859-1");
但是注意这里火狐浏览器比较特殊,因为他默认是以BASE64解码的,所以这块如果需要考虑火狐的问题的话还需要特殊处理一下。
先要获取客户端信息(通过获取请求头中的User-Agent信息)

//获取客户端信息
String ua = request.getHeader("User-Agent")
然后判断浏览器版本,做不同的处理(通过判断头信息中是否包含Firefox字符串来判断浏览器版本)
//判断客户端是否为火狐
if(ua.contains("Firefox")){
	//若为火狐使用BASE64编码
	filename = "=?utf-8?B?"+new BASE64Encoder()
.encode(filename.getBytes("utf-8"))+"?=";
}else{
	//否则使用UTF-8
	filename = URLEncoder.encode(filename,"utf-8");
文章来源:https://blog.csdn.net/muLanlh/article/details/135116518
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。