1、什么是上传和下载?

-  数据上传是指客户端向服务器上传数据,客户端向服务器发送的所有请求都属于数据上传。文件上传是数据上传的一种特例,指的是客户端向服务器上传文件。即将保存在客户端的文件上传至服务器中的一个副本,保存到服务器中。

-   数据下载是指客户端从服务器上获取数据的过程。文件下载是数据下载的一种特例,指的是客户端从服务器下载文件,即将原来保存在服务器中的文件下载到客户端中一个副本保存。通常我们对服务器所发出的请求,大多数是文件下载请求,从服务器中下载文本、图片、声音、视频等文件,然后由客户端浏览器对这些文件进行解析后,才可能看到这些多媒体信息。

-  但是我们这里所说的文件下载,指的是文件从服务器下载到浏览器后,浏览器并不直接解析,而是以附件的形式保存到客户端中。

-  上传与下载的文件可以是文本文件、图片、声音、视频等各种类型。

b8f92803d83bf1eeb80b10fbce113a91.png


2、文件上传的实现:

a、上传时对表单的要求:

-  文件上传要求客户端表单提交特殊的请求--multipart请求,即包含多部分数据的请求。所以文件上传表单对于表单数据的编码类型要求,必须为multipart/form-data。即要为<form>标签指定enctype属性值为“multipart/form-data”。(enctype,即encodingtype,编码类型。)

-  由于客户端上传文件的大小是不确定的,所以HTTP协议规定,文件上传的数据要存放于请求正文中,而不能够出现在url的地址栏中,因为地址栏中可以存放的数据量太小。也就是说,文件上传的表单,必须提交POST请求,而不能够提交GET请求。

	<!-- 
		文件上传对表单的要求:
		1、表单中的请求提交方式必须是POST
		2、表单中应指定所提交的情求为multipart请求
		3、表单中需要有file表单元素
	 -->
	 <form action="${pageContext.request.contextPath }/fileUpload" method-"POST" 
	 enctype="multipart/form-data">
	 	image:<input type="file" name="image" />
	 	<input type="submit" value="上传"/>
	 </form>


b、使用第三方工具实现文件上传:

-  需要使用到两个jar包:commons-fileupload-1.3.3.jar和commons-io-2.6.jar,jar包可以在apache官网中下载(commons-fileupload-1.3.3.jar依赖于commons-io-2.6.jar,如果只使用fileupload jar包,会运行程序时会抛出异常)

-  示例代码如下:

-  在index.jsp文件中填写的代码如下所示:

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<%@ page import="com.geeklicreed.listeners.Student"%>
<!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>
	<!-- 
		文件上传对表单的要求:
		1、表单中的请求提交方式必须是POST
		2、表单中应指定所提交的情求为multipart请求
		3、表单中需要有file表单元素
	 -->
	 <form action="${pageContext.request.contextPath }/fileUpload" method-"POST" 
	 enctype="multipart/form-data">
	 	name:<input type="text" name="username"/>
	 	image:<input type="file" name="image" />
	 	<input type="submit" value="上传"/>
	 </form>
</body>
</html>


- 为web应用添加一个Servlet,在web.xml文件中添加配置信息:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns="http://java.sun.com/xml/ns/javaee"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
	id="WebApp_ID" version="3.0">
	<display-name>el</display-name>
	<servlet>
		<servlet-name>fileUploadServlet</servlet-name>
		<servlet-class>com.geeklicreed.servlet.FileUploadServlet</servlet-class>
	</servlet>
	<servlet-mapping>
		<servlet-name>fileUploadServlet</servlet-name>
		<url-pattern>/fileUpload</url-pattern>
	</servlet-mapping>
</web-app>


-  自定义serlvet类,在doPost方法中编写简单实现文件上传的代码:

package com.geeklicreed.servlet;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
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 FileUploadServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
    protected void doPost(HttpServletRequest request, HttpServletResponse response) 
            throws ServletException, IOException {
        //判断请求是否为multipart请求
        if(!ServletFileUpload.isMultipartContent(request)){
            throw new RuntimeException("当前请求不支持文件上传");
        }
        try {
            //创建一个FileItem工厂
            DiskFileItemFactory factory = new DiskFileItemFactory();
            //创建文件上传核心组件
            ServletFileUpload servletFileUpload = new ServletFileUpload(factory);
            //解析请求,获取到所有的item
            List<FileItem> items = servletFileUpload.parseRequest(request);
            //遍历items
            for(FileItem item : items){
                if(item.isFormField()){  //若item为普通表单项
                    String fieldName = item.getFieldName();  //获取表单项名称
                    String fieldValue = item.getString();  //获取表单项的值
                    System.out.println(fieldName + " = " + fieldValue);
                }else{  //若item为文件表单项
                    //获取上传文件原始名称
                    String fileName = item.getName();   
                    //获取输入流,其中有上传 文件的内容
                    InputStream is = item.getInputStream();
                    //获取文件保存在服务器的路径
                    String path = this.getServletContext().getRealPath("/images");
                    //创建目标文件,将用来保存上传文件
                    File file = new File(path, fileName);
                    //将输入流中的数据写入到输出流中
                    OutputStream os = new FileOutputStream(file);
                    //将输入流中的数据写入输出流中
                    int len = -1;
                    byte[] buf = new byte[1024];
                    while((len = is.read(buf)) != -1){
                        os.write(buf, 0, len);
                    }
                    
                    os.close();
                    is.close();
                }
            }
        } catch (FileUploadException e) {
            e.printStackTrace();
        }
    }

}


-  启动web应用,访问index.jsp,添加图片后,点击“上传”,可以发现控制台打印结果,在工作空间web项目的指定目录可以看到图片已经上传:(在Eclipse中,默认会把Web项目放到Eclipse的工作空间下的.metadata\.plugins\org.eclipse.wst.server.core\tmp0(或者是tmp1)\wtpwebapps\下)

b21e32099f0c94dbd9f2c5ce326a994f.png

729ee6601f720fb6fd4c181782ac64b3.png

314014bfe306dd1b3104d4a0c3cccff5.png



-  FileUploadServlet类中的代码相关解释:(也包含使用fileupload jar包文件上传时的常用方法)

-  org.apache.commons.fileupload.disk.DiskFileItemFactory类:默认的FileItemFactory实现类。这个实现创建FileItem实例(文件条目)。如果是小条目,会将其存入内存中,如果是大条目,会将其存入硬盘中的临时文件。大小条目数据的临界值,是可配置的。

The default FileItemFactory
 implementation. This implementation creates FileItem instances which keep their
 content either in memory, for smaller items, or in a temporary file on disk,
 for larger items. The size threshold, above which content will be stored on
 disk, is configurable, as is the directory in which temporary files will be
 created.


-  setRepository(File repository)方法:设置临时存储文件(这个文件大小大于指定配置临界值)存放的目录位置。

-  setSizeThreshold(int sizeThreshold)方法:设置临界值(单位为字节),当超过这个值时文件将会直接写入硬盘中。(如sizeThreshold为1024 * 1024,则为1兆)

voidsetRepository(File repository)

Sets the directory used to temporarily store files that are larger than the configured size threshold.

voidsetSizeThreshold(int sizeThreshold)

Sets the size threshold beyond which files are written directly to disk.


-  org.apache.commons.fileupload.servlet.ServletFileUpload类:处理文件上传的高级API。(如何存储各个部分的数据是由创建它们的工厂决定;给定的部分可能在内存中、磁盘上或者其他地方)

High level API for processing file uploads.
This class handles multiple files per single HTML widget, sent using multipart/mixed encoding type, as specified by RFC 1867.  Use parseRequest(HttpServletRequest) to acquire a list of FileItems associated with a given HTML
 widget.
How the data for individual parts is stored is determined by the factory
 used to create them; a given part may be in memory, on disk, or somewhere
 else.


-  parseRequest方法:解析请求,获取FileItem类型元素的List对象:

List<FileItem>parseRequest(HttpServletRequest request)

Processes an RFC 1867 compliant multipart/form-data stream.


-  ServletFileUpload类的父类org.apache.commons.fileupload.FileUploadBase的方法:

-  设置每个item的头部字符编码,其可以解决文件名的中文乱码问题:(若tomcat版本较低时,可能会出现文件名中文乱码)

voidsetHeaderEncoding(String encoding)

Specifies the character encoding to be used when reading the headers of individual part.


-  setFileSizeMax(long fileSizeMax)方法设置单个上传文件允许上传的最大边界值。

-  setSizeMax(long sizeMax)方法设置一次上传所有文件的总和最大边界值。(单位都为字节)

voidsetFileSizeMax(long fileSizeMax)

Sets the maximum allowed size of a single uploaded file, as opposed to getSizeMax().

voidsetSizeMax(long sizeMax)

Sets the maximum allowed size of a complete request, as opposed to setFileSizeMax(long).


-  isMultipartContent(HttpServletRequest req)方法:判断该请求是否包含multipart内容

static booleanisMultipartContent(HttpServletRequest req)

Deprecated. 

1.1 Use the method on ServletFileUpload instead.


-  org.apache.commons.fileupload.FileItem接口:表示在multipart/form-data的POST请求中内的一个文件或者表单条目。

This class represents a file or form item that was received within a multipart/form-data POST request.


-  isFormField方法:判断FileItem实例是否为一个普通form项:

booleanisFormField()

Determines whether or not a FileItem instance represents a simple form field.


-  getFieldName方法:获取对应文件条目(即单个表单项)的name属性值:

StringgetFieldName()

Returns the name of the field in the multipart form corresponding to this file item.


-  getName方法:获取客户端文件系统中的原上传文件名:(包含后缀名)

StringgetName()

Returns the original filename in the client's filesystem, as provided by the browser (or other client software).


-  getInputStream方法:获取输入流,用于取出上传文件中的内容:

InputStreamgetInputStream()

Returns an InputStream that can be used to retrieve the contents of the file.


-  delete方法:删除任何相关联的临时磁盘文件:

voiddelete()

Deletes the underlying storage for a file item, including deleting any associated temporary disk file.


3、文件下载的实现:

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

-  该下载方式的缺点很明显,不同的浏览器,以及相同的浏览器所安装的插件不同,那么其对于资源的解析能力也就不同,其是否弹出另存为对话框的情况也就不一样。决定权由浏览器掌握。

-  如在jsp文件中添加如下代码,在ff浏览器中点击,直接在浏览器中打开该图片:

<a href="${pageContext.request.contextPath}/images/timg.jpg">timg.jpg</a>


b、Servlet方式的文件下载:

-  示例代码:

-  jsp文件中的内容和web.xml文件中添加的配置:

<a href="${pageContext.request.contextPath}/download">download image</a>
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://java.sun.com/xml/ns/javaee"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
    id="WebApp_ID" version="3.0">
    <servlet>
        <servlet-name>downloadServlet</servlet-name>
        <servlet-class>com.geeklicreed.servlet.DownloadServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>downloadServlet</servlet-name>
        <url-pattern>/download</url-pattern>
    </servlet-mapping>
</web-app>


-  自定义Servlet类中添加如下代码:

package com.geeklicreed.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 {
        String fileName = "火拳艾斯.jpg";
        byte[] bytes = fileName.getBytes("utf-8");
        fileName = new String(bytes, "ISO8859-1");
        //修改响应的头部属性content-disposition值为attachment
        response.setHeader("content-disposition", "attachment;filename=" + fileName );
        //获取连接服务器端资源文件的输入流
        InputStream is = this.getServletContext().getResourceAsStream("/images/timg.jpg");
        //获取输出流
        ServletOutputStream os = response.getOutputStream();
        //将输入流中的数据写入到输出流中
        int len = -1;
        byte[] buf = new byte[1024];
        while((len = is.read(buf)) != -1){
            os.write(buf, 0 , len);
        }
        
        os.close();
    }

}


-  如果想要浏览器下载的方式是以附件的形式下载,需要修改响应的头部属性content-disposition值为attachment;filename=文件名 + 后缀名。(filename表示以附件形式下载显示的文件名)

- javax.servlet.ServletContext接口的getResourceAsStream方法:获取web应用中的指定路径下的资源,以字节输入流InputStream的形式返回。

-  java.io.InputStream getResourceAsStream(java.lang.String path) : Returns the resource located at the named path as an InputStream object.


c、启动web应用,点击超链接后,可以看到文件以附件的形式下载:

e4f78ad2d6a233bc7e5460214d89d0b8.png


4、附加说明:

-  上传文件目录的管理:当用户上传的文件过多,而单个目录能够存放的文件个数有限,(windows系统下单文件夹中最大可有65,534个文件)所以需要上传的文件有时需要分目录进行管理。

-   可以使用java.io.File类的mkdir()方法来在当前目录中创建直接子目录,或者使用mkdirs()方法创建当前目录的多层子目录:

booleanmkdir()

Creates the directory named by this abstract pathname.

booleanmkdirs()

Creates the directory named by this abstract pathname, including any necessary but nonexistent