JavaWeb(12)之文件的上传和下载

文件上传

文件上传的概述

文件上传:将本地的文件通过流写入到服务器的过程。

实际开发中有很多应用:
QQ空间上传图片。
招聘网站上传简历。

文件上传的技术

在这里插入图片描述

文件上传的要素

文件上传的三个要素:

  • 表单的提交的方式需要是POST
  • 表单中需要有 <input type=“file">元素,需要有name属性和值
  • 表单enctype=“multipart/form-data”

POST请求的编码格式

Post请求的两种编码格式:application/x-www-form-urlencoded和multipart/form-data

首先我们需要明白在html中的enctype属性,
enctype:规定了form表单在发送到服务器时候编码方式。他有如下的三个值。

  • ①application/x-www-form-urlencoded。默认的编码方式。但是在用文本的传输和MP3等大型文件的时候,使用这种编码就显得 效率低下。
  • ②multipart/form-data 。 指定传输数据为二进制类型,比如图片、mp3、文件。
  • ③text/plain。纯文体的传输。空格转换为 “+” 加号,但不对特殊字符编码。

post默认application/x-www-form-urlencoded方式提交:只能获取获取文件的名字不能获取文件的内容。

multipart/form-data方式提交:可以获取文件的内容

总结:文件上传必须使用multipart/form-data编码方式提交

文件上传的原理分析

JSP页面:

<body>
	<h1>文件上传</h1>
	<!-- 
		文件上传三要素:
			*	表单需要POST提交
			*	表单中需要文件上传项,必须有name的属性和值
			*	表单的enctype属性必须是multipart/form-data
	 -->
	<form action="" method="post" enctype="multipart/form-data">
		文件描述:<input type="text" name="info"><br>
		文件上传:<input type="file" name="upload"><br>
		<input type="submit" value="提交">
	</form>
</body>

在这里插入图片描述

文件上传的案例

第一步:引入文件上传相关的jar包:

common-io下载
在这里插入图片描述
common-fileupload下载
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
第二部:编写文件上传的页面

<body>
	<h1>文件上传</h1>
	<!-- 
		文件上传三要素:
			*	表单需要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>

第三部:编写文件上传的Servlet
FileItem类的常用方法
解决tomcat每次重启丢失项目文件的问题

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.annotation.WebServlet;
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.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;

/**
 * 文件上传的Servlet
 */
@WebServlet("/UploadServlet")
public class UploadServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;

	@Override
	protected void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		try {
			// 1.创建磁盘文件项工厂
			DiskFileItemFactory diskFileItemFactory = new DiskFileItemFactory();
			// 2.创建一个核心的解析类,将磁盘文件项放入
			ServletFileUpload fileUpload = new ServletFileUpload(diskFileItemFactory);
			// 3.利用核心类解析Request,解析后得到多个请求部分。返回一个LIst集合,List集合装的是每个请求部分的内容(FileItem文件项)。
			List<FileItem> list = fileUpload.parseRequest(request);
			// 4.遍历List集合,会得到代表每个部分的文件项的对象。根据文件项判断是否是文件上传项。
			for (FileItem item : list) {
				// 判断这个文件是普通项还是文件上传项
				if (item.isFormField()) {
					// 普通项
					// 接收普通项的值:(接收值不在使用request.getParameter())
					String name = item.getFieldName();// 获得普通项的名称
					String value = item.getString("UTF-8");// 获得普通项的值
					System.out.println(name + "  " + value);
				} else {
					// 文件上传项
					// 获得文件上传项的文件名称
					String fileName = item.getName();
					// IE中获得的是绝对路径,所以需要分割
					int i = fileName.lastIndexOf("//");
					if (i != -1) {
						// 使用的是老版本浏览器
						fileName = fileName.substring(i + 1);
					}
					// 获得文件上传项的数据
					InputStream is = item.getInputStream();
					// 获得文件上传的路径:磁盘绝对路径
					String realPath = getServletContext().getRealPath("/upload");
					// 创建一个输出流,写入到设置的路径中
					OutputStream os = new FileOutputStream(realPath + "/" + fileName);
					// 两个流对接
					int len = 0;
					byte[] b = new byte[1024];
					while ((len = is.read(b)) != -1) {
						os.write(b, 0, len);
					}

					// 释放资源
					os.close();
					is.close();
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

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

}

运行结果:
在这里插入图片描述
提交后:
控制台
在这里插入图片描述
文件夹
在这里插入图片描述

文件上传的API

Commons FileUpload 1.4 API文档

DiskFileItemFactory:磁盘文件项工厂

  • 构造方法
    在这里插入图片描述
    DiskFileltemFactory()

DiskFileltemFactory(int sizeThreshold,File repostory)
sizeThreshold:设置文件上传的缓冲区的大小,默认值为10kb。
repository:设置文件上传过程中产生临时文件存放的路径。
如果在上传文件的过程中文件的大小超过了缓冲区的大小,就会产生临死文件。

  • 方法
    这里重点学习两个方法
    在这里插入图片描述

setSizeThreshold():设置缓冲区的大小
setRepository() :设置临时文件存放的路径

案例:
写在文件上传的Servlet中

			// 1.1设置缓冲区大小
			diskFileItemFactory.setSizeThreshold(1024 * 1024 * 3);// 设置缓存区大小为3M
			// 1.2设置临时文件存放的路径
			// 获得临时文件存放的路径
			String temp = getServletContext().getRealPath("/temp");
			diskFileItemFactory.setRepository(new File(temp));

运行:
在上传后文件大于缓冲区会在temp文件中存放一个临时文件,这个文件也是可以删除的,在后面会学到。
在这里插入图片描述

ServletFileUpload:核心解析类

  • 构造方法
    我们使用的是有参的构造方法,将FileItemFactory传递进去
    在这里插入图片描述
  • 方法
    在这里插入图片描述
    isMultipartContext:用来判断表单的enctype属性是否正确
    parseRequest:解析Request对象,返回一个List集合(每个部分的对象FileItem)

ServletFileUpload类的方法不多,他的父类FileUploadBase方法比较多:

学习几个比较常用的:
设置单个文件的大小
在这里插入图片描述
设置上传文件的总大小
在这里插入图片描述
设置中文文件名乱码的问题
在这里插入图片描述
设置监听文件上传的进度
在这里插入图片描述

FileItem文件项

FileItem类的常用方法
方法:
判断表单项是普通项还是文件上传项,如果为true代表是普通项。
在这里插入图片描述
普通项的方法:
获得普通项的名称
在这里插入图片描述
获得普通项的值
在这里插入图片描述
文件上传项的方法:
用来获得文件名的方法
在这里插入图片描述
获得文件内容的方法
在这里插入图片描述
获得文件的大小
在这里插入图片描述
返回文件的类型,后缀名。
在这里插入图片描述
删除文件上传过程中的临时文件
在这里插入图片描述

JS控制多文件上传

案例需求分析

每点一次添加就添加一个文件上传的模块,点击上传后一起上传。
在这里插入图片描述

案例实现

实现多文件上传只需要利用JS代码即可。
然后上传到Servlet即可

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
<script type="text/javascript">
	function add() {
		//获得id为div1的元素
		var div1Element = document.getElementById("div1");
		div1Element.innerHTML += "<div><input type='file' name='upload'/><input type='button' value='删除' 	οnclick='del(this)'/></div>"
	}
	
	function del(who) {
		who.parentNode.parentNode.removeChild(who.parentNode);
	}
</script>
</head>
<body>
	<h1>多文件上传</h1>
	<form action="${ pageContext.request.contextPath }/UploadServlet" method="post" enctype="multipart/form-data">
		<input type="button" value="添加" onclick="add()">
		<input type="submit" value="上传" >
		<div id="div1">
			
		</div>
	</form>
</body>
</html>

文件上传兼容浏览器问题及解决

问题描述

如果使用IE老版本的浏览器出现一个文件名称获取错误问题。
IE老版本获取文件名称的时候,会带有路径。

问题解决

将字符串分割即可

	int i = fileName.lastIndexOf("//");
	if (i != -1) {
		// 使用的是老版本浏览器
		fileName = fileName.substring(i + 1);
	}

文件上传同一个目录下文件同名的问题及解决

问题描述

张三向服务器上传了一个文件aa.txt内容是hello world。李四向服务器上传了一个文件aa.txt内容helloJava。后上传的文件将先上传的文件覆盖了。

问题解决

创建一个工具类:
传递一个文件名,返回一个唯一的文件名

/**
 * 文件上传的工具类
 * 
 * @author 25858
 *
 */
public class UploadUtils {
	/**
	 * 传递一个文件名,返回一个唯一的文件名
	 */
	public static String getUuidFilename(String fileName) {
		// 在java的API中有一个类UUID可以产生随机的字符串
		// UUID.randomUUID().toString();
		// 获得文件名的扩展名
		int index = fileName.lastIndexOf(".");
		String extetions = fileName.substring(index);
		return UUID.randomUUID().toString() + extetions;
	}
}

在使用的过程中将文件名传递即可,然后在输出流中使用返回的唯一文件名。

文件上传同一个目录下存放文件过多的问题及解决

问题描述

现在所有的用户都上传文件,如果网站访问量比较大,如果都上传到同一个目录下,在同一个目录下存放的文件太多了,也会对程序有影响(其实打开该目录的时候,都会很卡,更别说读写操作)。

问题解决

在这里插入图片描述
分析目录分离算发:
在这里插入图片描述
同样将目录分离算发写在工具类中:

	/**
	 * 目录分离的算法实现
	 * 两级目录
	 */
	public static String getRealPath(String uuidFileName) {
		int code1 = uuidFileName.hashCode();
		int d1 = code1 & 0xf;
		int code2 = code1 >>> 4;
		int d2 = code2 & 0xf;
		return "/" + d1 + "/" + d2;
	}

在Servlet中使用:
将唯一文件名传递,然后组合成新的目录路径,在输出流中使用新的路径。

					// 获得文件上传的路径:磁盘绝对路径
					String realPath = getServletContext().getRealPath("/upload");
					// 进行目录分离
					String path = UploadUtils.getRealPath(uuidFileName);
					String newPath = realPath + path;
					File file = new File(newPath);
					// 判断文件是否创建
					if (!file.exists()) {
						file.mkdirs();
					}
					// 创建一个输出流,写入到设置的路径中
					FileOutputStream os = new FileOutputStream(newPath + "/" + uuidFileName);

文件下载

文件下载的概述

文件下载:将服务器上的个文件,通过流写入到客户端上。

文件下载的方式

在这里插入图片描述

超链接方式下载

超链接方式直接写文件的绝对路径即可

<body>
	<h1>文件下载:超链接的方式</h1>
	<a href="${ pageContext.request.contextPath }/download/111.jpg">111.jpg</a>
	<a href="${ pageContext.request.contextPath }/download/aaa.zip">aaa.zip</a>
</body>

如果浏览器支持这个格式的文件就会直接打开,如果浏览器不支持这个格式的文件才会提示下载。

手动编码方式下载

编写文件下载的页面:

	<h1>文件下载:手动编码的方式</h1>
	<a href="${ pageContext.request.contextPath }/DownloadServlet?filename=111.jpg">111.jpg</a>
	<a href="${ pageContext.request.contextPath }/DownloadServlet?filename=aaa.zip">aaa.zip</a>

文件下载的Servlet:

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

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

/**
 * 文件下载的Servlet
 */
@WebServlet("/DownloadServlet")
public class DownloadServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;

	@Override
	protected void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		// 1.接收参数
		String filename = request.getParameter("filename");

		// 2.下载:设置两个头和一个流
		// 设置Context-Type
		// 获得文件内容类型
		String type = getServletContext().getMimeType(filename);
		// 设置发送到客户端的响应时的内容类型
		response.setContentType(type);
		// 设置Content-Disposition
		// 让浏览器收到这份资源的时候, 以下载的方式提醒用户,而不是直接展示。
		response.setHeader("Content-Disposition", "attachment;filename=" + filename);
		// 设置一个代表了文件的输入流
		String path = getServletContext().getRealPath("/download");
		InputStream is = new FileInputStream(path + "/" + filename);
		// 使用response获取输出流
		OutputStream os = response.getOutputStream();
		// 两个流对接
		int len = 0;
		byte[] b = new byte[1024];
		while ((len = is.read(b)) != -1) {
			os.write(b, 0, len);
		}
		os.close();
		is.close();
	}

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

}

中文文件的下载

创建一个转换中文名称的工具类:
因为中文在提示下载时是显示不出来的,所以要转换一下。
使用时需要将文件名称和HttpServletRequest 传入其中

mport java.io.UnsupportedEncodingException;
import java.net.URLEncoder;

import javax.servlet.http.HttpServletRequest;

import sun.misc.BASE64Encoder;

/**
 * 转换中文名称的类
 * 
 * @author 25858
 *
 */
public class DownUtils {
	public static String filenameEncoding(String filename, HttpServletRequest request)
			throws UnsupportedEncodingException {
		// 获得请求头中的User-Agent
		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 {
			// 其它浏览器
			filename = URLEncoder.encode(filename, "UTF-8");
		}
		return filename;
	}
}

Servlet类:
在Servlet中使用要注意,将判断浏览器放在获得文件内容类型的下面,还要将输入流中的路径单独创建,因为路径使用的是获取到的正常值,而不是转换后的值。

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

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

import com.wxw.utils.DownUtils;

/**
 * 文件下载的Servlet
 */
@WebServlet("/DownloadServlet")
public class DownloadServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;

	@Override
	protected void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		// 1.接收参数
		String filename = request.getParameter("filename");
		// 2.下载:设置两个头和一个流
		// 设置Context-Type
		// 获得文件内容类型
		String type = getServletContext().getMimeType(filename);
		// 定义一个代表该文件的路径
		String path = getServletContext().getRealPath("/download");
		File file = new File(path + "/" + filename);
		// 判断浏览器并转换中文名称
		filename = DownUtils.filenameEncoding(filename, request);
		System.out.println(filename);
		// 设置发送到客户端的响应时的内容类型
		response.setContentType(type);
		// 设置Content-Disposition
		// 让浏览器收到这份资源的时候, 以下载的方式提醒用户,而不是直接展示。
		response.setHeader("Content-Disposition", "attachment;filename=" + filename);
		// 设置一个代表了文件的输入流
		InputStream is = new FileInputStream(file);
		// 使用response获取输出流
		OutputStream os = response.getOutputStream();
		// 两个流对接
		int len = 0;
		byte[] b = new byte[1024];
		while ((len = is.read(b)) != -1) {
			os.write(b, 0, len);
		}
		os.close();
		is.close();
	}

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

}

案例:给定目录下的文件下载

案例需求

给定一个目录(这个目录可以是任意盘符下的任意路径-这个路径下有多少级目录,每级目录中有多少个文件都是未知的)。
将这个路径中的文件显示到页面上,在页面上给每个问题件都提供响应下载的链接,当点击这个链接的时候,对该文件进行下载。

案例分析

树形数据的遍历和过滤
在这里插入图片描述
获取节点的方式:
在这里插入图片描述

文件列表显示功能

Java队列(Queue)了解及使用
深入理解FIFO(包含有FIFO深度的解释)

<body>
	<h1>树形遍历</h1>
	<%
		// 1.创建一个队列
		Queue<File> queue = new LinkedList<File>();
		// 2.将根节点入队
		File root = new File("D:\\壁纸\\壁纸02\\动漫");
		queue.offer(root);
		//判断这个队列是否为空,不为空需要遍历
		while (!queue.isEmpty()) {
			//将根节点出队
			File file = queue.poll();
			//获得更节点下的所有子节点
			File[] files = file.listFiles();
			//遍历所以子节点
			for (File f : files) {
				//判断此节点是否为叶子节点
				if (f.isFile()) {
	%>
	
	<h4>
		<a href="#"> <%=f.getName()%>
		</a>
	</h4>
	
	<%
		} else {
					queue.offer(f);
				}
			}
		}
	%>
</body>

下载代码实现

/**
 * 树形文件下载的代码实现
 */
@WebServlet("/DownloadListServlet")
public class DownloadListServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;

	@Override
	protected void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		// 1.接收参数
		String path = request.getParameter("filename");
		File file = new File(path);

		// 2.下载:设置两个头和一个流
		// 设置Context-Type
		// 获得文件内容类型
		String filename = file.getName();
		String type = getServletContext().getMimeType(filename);
		// 设置发送到客户端的响应时的内容类型
		response.setContentType(type);
		// 判断浏览器
		filename = DownUtils.filenameEncoding(filename, request);
		System.out.println(filename);
		// 设置Content-Disposition
		// 让浏览器收到这份资源的时候, 以下载的方式提醒用户,而不是直接展示。
		response.setHeader("Content-Disposition", "attachment;filename=" + filename);
		// 设置一个代表了文件的输入流
		InputStream is = new FileInputStream(file);
		// 使用response获取输出流
		OutputStream os = response.getOutputStream();
		// 两个流对接
		int len = 0;
		byte[] b = new byte[1024];
		while ((len = is.read(b)) != -1) {
			os.write(b, 0, len);
		}
		os.close();
		is.close();
	}

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

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值