Javaweb中文件的上传和下载

文件上传

文件上传指的是用户通过浏览器向服务器上传某个文件,服务器接收到该文件后会将该文件存储在服务器的硬盘中,通常不会存储在数据库中,这样可以减轻数据库的压力并且在文件的操作上更加灵活,常见的功能是上传头像图片。
文件上传的原理
所谓的文件上传就是服务器端通过request对象获取输入流,将浏览器端上传的数据读取出来,保存到服务器端。
文件上传的要求

  • 提供form表单,表单的提交方式必须是post
  • form表单中的enctype属性必须是multipart/form-data
  • 表单中提供input type=”file”上传输入域

代码示例

创建一个upload.html的文件,里面提供上传的按钮:

<form enctype="multipart/form-data" action="/upload1" method="post" >
        <input type="file" name="photo"/><br/>
        <input type="submit" value="上传"/><br/>
    </form>

创建servlet用来处理用户上传的文件:

package com.monkey1024.servlet;

import java.io.IOException;
import java.io.InputStream;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 文件上传
 */
public class UploadServlet1 extends HttpServlet {
    private static final long serialVersionUID = 1L;

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

        //获取请求的输入流
        InputStream is = request.getInputStream();

        //读取输入流中的数据
        int len = 0;
        byte[] b = new byte[1024];
        while((len=is.read(b))!=-1){
            //打印文件的字节内容
            System.out.println(new String(b,0,len));
        }
    }

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }

}

FileUpload工具的使用

在实际开发中通常会借助第三方工具来实现上传功能,应用较多的是apache旗下的Commons-fileupload。
Apache 开源组织提供了一个用来处理表单文件上传的一个开源组件( Commons-fileupload ),该组件性能优异,并且其API使用极其简单,可以让开发人员轻松实现web文件上传功能,因此在web开发中实现文件上传功能,通常使用Commons-fileupload组件实现。
在使用该工具实现上传功能时,首先需要导入Commons-fileupload和commons-io两个jar包。
下载地址:https://pan.baidu.com/s/1oMBGAKBeei3hifW6AiuQHg
提取码:7m7c

代码示例

package com.monkey1024.servlet;

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;

/**
 * 使用commons fileupload完成上传
 */
public class UploadServlet2 extends HttpServlet {
    private static final long serialVersionUID = 1L;

    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        //1.判断表单是否支持文件上传。即:enctype="multipart/form-data"
        boolean isMultipartContent = ServletFileUpload.isMultipartContent(request);
        if (!isMultipartContent) {
            throw new RuntimeException("该请求不能完成文件上传");
        }
        //2.创建一个DiskFileItemfactory对象
        DiskFileItemFactory factory = new DiskFileItemFactory();
        //3.创建一个ServletFileUpload对象,该对象是上传的核心组件
        ServletFileUpload sfu = new ServletFileUpload(factory);
        //4.解析request对象,并得到一个表单项的集合
        try {
            List<FileItem> fileItems = sfu.parseRequest(request);
            //5.遍历该集合
            for (FileItem item : fileItems) {

                if (item.isFormField()) {
                    // 普通表单项
                    String fieldname = item.getFieldName();// 字段名
                    String fieldvalue = item.getString("UTF-8");//字段值
                    System.out.println(fieldname + "=" + fieldvalue);
                } else {
                    //上传表单项
                    //获取文件名
                    String fileName = item.getName();
                    //获取输入流
                    InputStream is = item.getInputStream();
                    //创建输出流
                    String path = this.getServletContext().getRealPath("/upload");
                    File file = new File(path,fileName);
                    FileOutputStream fos = new FileOutputStream(file);
                    //完成文件的复制
                    byte[] bytes = new byte[1024];
                    int len = -1;
                    while((len = is.read(bytes)) != -1){
                        fos.write(bytes, 0, len);
                    }
                }
            }
        } catch (FileUploadException e) {
            e.printStackTrace();
        }


    }

    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        doGet(request, response);
    }

}

注意:文件上传时在服务器中所存放的路径最好是用户不能直接访问的路径,这样可以保证文件的安全。

解析原理:

commons-fileupload解析原理

临时目录

文件由浏览器通过网络上传到服务器,并不是直接通过一条网络线路将所有请求数据发送到了服务器的。而是将这些数据分为了很多个数据包,这些数据包分别被编号后,经由不同的网络线路最终发送到了服务器中。这些数据包到达服务器的时间会根据不同的网络线路的情况不同,分别先后到达服务器,顺序是不定的。因此服务器会在其临时目录中,创建一个临时文件,将这些数据包进行拼接组装。Tomcat 默认情况下的临时目录是 Tomcat 服务器安装目录的 temp 子目录。我们也可以修改临时目录的默认位置。Apache 的 FileUpload 支持设置创建临时文件的最小临界值,即只有上传的文件大小超出这个值,才会创建临时文件。通过 DiskFileItemFactory 的 setSizeThreshold()方法可以设置临界值,单位为字节。通过 DiskFileItemFactory 的 setRepository()方法可以指定临时目录。

在Web文件夹下创建temp文件夹。
修改上面的代码如下:

package com.monkey1024.servlet;

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;

/**
 * 临时目录
 */
public class UploadServlet3 extends HttpServlet {
    private static final long serialVersionUID = 1L;

    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        //1.判断表单是否支持文件上传。即:enctype="multipart/form-data"
        boolean isMultipartContent = ServletFileUpload.isMultipartContent(request);
        if (!isMultipartContent) {
            throw new RuntimeException("该请求不能完成文件上传");
        }
        //2.创建一个DiskFileItemfactory对象
        DiskFileItemFactory factory = new DiskFileItemFactory();
        //******指定临时文件存储的目录
        String tempPath = this.getServletContext().getRealPath("/temp");
        System.out.println(tempPath);
        factory.setRepository(new File(tempPath));
        //3.创建一个ServletFileUpload对象,该对象是上传的核心组件
        ServletFileUpload sfu = new ServletFileUpload(factory);
        //4.解析request对象,并得到一个表单项的集合
        try {
            List<FileItem> fileItems = sfu.parseRequest(request);
            //5.遍历该集合
            for (FileItem item : fileItems) {

                if (item.isFormField()) {
                    // 普通表单项
                    String fieldname = item.getFieldName();// 字段名
                    String fieldvalue = item.getString("UTF-8");//字段值
                    System.out.println(fieldname + "=" + fieldvalue);
                } else {
                    //上传表单项
                    //获取文件名
                    String fileName = item.getName();
                    //获取输入流
                    InputStream is = item.getInputStream();
                    //创建输出流
                    String path = this.getServletContext().getRealPath("/upload");
                    File file = new File(path,fileName);
                    FileOutputStream fos = new FileOutputStream(file);
                    //完成文件的复制
                    byte[] bytes = new byte[1024];
                    int len = -1;
                    while((len = is.read(bytes)) != -1){
                        fos.write(bytes, 0, len);
                    }
                    /*
                        临时文件一旦用完,就可将其删除了,否则占用服务器的硬盘空间。
                        需要注意的是,对于临时文件的删除,需要在 IO 流关闭后,否则,无法删除。
                    */
                    is.close();
                    fos.close();
                    item.delete();
                }
            }
        } catch (FileUploadException e) {
            e.printStackTrace();
        }


    }

    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        doGet(request, response);
    }

}

需要注意的是要将temp文件夹中的临时文件删除,这个需要在IO流关闭后才能删除。
当上传的文件大小超过10kb时就会将文件放到临时目录中了。10kb是默认值,可以通过DiskFileItemFactory中的setSizeThreshold方法修改:

factory.setSizeThreshold(1024 * 1024 *3);    

上面代码的作用是当上传文件大小超过3MB时才会使用临时文件。

上传文件名重名的问题

在服务器中已有文件名叫做monkey.jpg的文件,当用户再次上传同名的文件时,会将之前的文件名覆盖,这样会出现问题。为了解决该问题,可以将上传的文件重命名,有两种方式:

  • 在文件名中添加系统时间。
  • 在文件名中添加uuid。

uuid是Universally Unique Identifier的缩写,中文是通用统一识别码,uuid具有唯一性,uuid的生成跟系统的时间、mac地址、时间序列、随机数有关,所以通常所生成的uuid是不会重复的,两个相同的uuid出现的概率非常低(比陨石撞击地球的概率还要低)。

代码示例:

//重命名文件名
fileName = fileName.substring(fileName.lastIndexOf("\\") + 1);
//在文件名中添加系统时间
fileName = System.currentTimeMillis() + "_" + fileName;
//在文件名中添加uuid
fileName = UUID.randomUUID() + "_" + fileName;

上传文件的大小

对于上传文件的大小,可以通过 ServletFileUpload 的 setFileSizeMax()与 setSizeMax()方法进行控制。 setFileSizeMax()用于设置单个文件上传的最大值,而 setSizeMax()用于设置单次上传的最大值。即若一次上传多个文件,每个文件的大小边界值与所有文件加起来的最大小值。

//3.创建一个ServletFileUpload对象,该对象是上传的核心组件
ServletFileUpload sfu = new ServletFileUpload(factory);
//设置单个文件的最大值(2M)
sfu.setFileSizeMax(1024 * 1024 *2);
//设置上传文件的总大小(5M)
sfu.setSizeMax(1024 * 1024 *5);

创建目录

无论是 Windows 系统、Linux 系统,还是其它系统,其目录中所包含的文件数量是有上
限的。所以对于上传的文件,应该分目录进行管理。若文件较多,则可按照年、月、日创建多级子目
录。这样,即方便管理,又不会超出目录的文件数量上限。

//获取输入流
InputStream is = item.getInputStream();

String path = this.getServletContext().getRealPath("/upload");


//获取当前系统时间的年月日
LocalDate now = LocalDate.now();
int year = now.getYear();
int month = now.getMonthValue();
int day = now.getDayOfMonth();

//在upload下分别创建年、月、日三级子目录
path = path + "/" + year + "/" + month + "/" +day;
//创建父目录
File parentDir = new File(path);
//如果父目录不存在,则创建
if(!parentDir.exists()){
    parentDir.mkdirs();
}

文件下载

超链接下载
超链接下载是指,将下载资源作为超链接的链接目的文件出现。若浏览器可以解析该资源文件,则将在浏览器上直接显示文件内容;若浏览器不支持该文件的解析,则会弹出另存为对话框,要求用户保存。

在Web文件夹下创建download文件夹,里面放入一些文件。

创建html:

<body>
    <a href="/download/aaa.zip">下载aaa文件</a>
</body>

使用servlet实现下载

若要使下载的文件以附件的形式出现在浏览器,则需要设置响应头的属性content-disposition 的值为 attachment,且需要指定浏览器下载后显示的文件名。即需要指定content-disposition 的值为 attachment;filename=文件名。

package com.monkey1024.servlet;

import java.io.IOException;
import java.io.InputStream;

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 {
    private static final long serialVersionUID = 1L;

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //设置响应头属性值,使文件以附件形式进行下载
        response.setHeader("content-disposition", "attachment;filename=monkey1024.png");

        //获取输入流
        InputStream is = this.getServletContext().getResourceAsStream("/download/monkey1024.png");
        ServletOutputStream os = response.getOutputStream();

        //复制文件
        byte[] bytes = new byte[1024];
        int len = 1;
        while((len = is.read(bytes)) != -1){
            os.write(bytes, 0, len);
        }
    }

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }

}

创建html:

<a href="/download">下载图片文件</a>

解决文件名乱码问题
只需将文件名设置编码即可:

String fileName = "小猴子.png";
fileName = new String(fileName.getBytes("utf-8"),"iso8859-1");
//设置响应头属性值,使文件以附件形式进行下载
response.setHeader("content-disposition", "attachment;filename="+fileName);