文件上传
文件上传的概述
文件上传:将本地的文件通过流写入到服务器的过程。
实际开发中有很多应用:
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
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);
}
}