文件的上传
文件上传的概述
问题:什么是文件上传?为什么使用文件上传?
- 就是将客户端资源,通过网络传递到服务器端。
- 就是因为数据比较大,我们必须通过文件上传才可以完成将数据保存到服务器端操作.
- 文件上传的本质:就是IO流的操作。
演示:文件 上传应该 怎样操作?
浏览器端:
1.method=post 只有post才可以携带大数据
2.必须使用<input type='file' name='f'>要有name属性
3.encType="multipart/form-data"
服务器端:
request对象是用于获取请求信息。
它有一个方法 getInputStream(); 可以获取一个字节输入流,通过这个流,可以读取到
所有的请求正文信息.
文件上传原理:
- 浏览器端注意上述三件事,在服务器端通过流将数据读取到,在对数据进行解析.
- 将上传文件内容得到,保存在服务器端,就完成了文件上传。
文件上传的体验
文件上传需要用到下面的组件
<input type="file" name="f" ><br>
并且在form表单中要使用
method="post" encType="multipart/form-data"
-
method="post"是必要的因为是大数据的传输,
-
encType="multipart/form-data"的原因是。不然只能取到输入框中的信息,f=C%3A%5CUsers%5CAdministrator%5CDesktop%5Ca.txt
-
设置encType="multipart/form-data"之后的效果:
编写上传文件的表单:
upload1.jsp
<body>
<form action="${pageContext.request.contextPath}/upload1" method="post" encType="multipart/form-data">
普通文本框:<input type="text" name="content"><br>
文件上传:<input type="file" name="f" ><br>
<input type="submit" value="提交" >
</form>
</body>
编写Servlet
Upload1.java
@WebServlet("/upload1")
public class Upload1 extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
ServletInputStream is = request.getInputStream();
byte[] b = new byte[1024];
int len = -1;
while ((len = is.read(b)) != -1) {
System.out.println(new String(b, 0, len));
}
is.close();
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
运行效果:
工具类进行文件的上传
-
在上面我们只是取到了文件的信息,此时我们里成功已经不远了,前面说过,文件的上传本质就是文件之间的copy
-
接下来我们将使用工具类实现文件的上传操作
-
在实际开发中,不需要我们进行数据解析,完成文件上传。因为我们会使用文件上传的工具,它们已经封装好的,提供API,只要调用它们的API就可以完成文件上传操作.
我们使用的commons-fileupload,它是apache提供的一套开源免费的文件上传工具。
-
使用commons-fileupload
-
步骤
-
导入jar包
commons-fileupload-1.2.1.jar 文件上传
commons-io-1.4.jar 它是提供的io工具.
介绍commons-fileupload
它有三个核心
1.DiskFileItemFactory类
2.ServletFileUpload类
3.FileItem -
快速入门:
-
创建upload2.jsp页面
<body> <form action="${pageContext.request.contextPath}/upload2" method="post" encType="multipart/form-data"> 普通文本框:<input type="text" name="content"><br> 文件上传:<input type="file" name="f" ><br> <input type="submit" value="提交" > </form> </body>
-
创建Upload2
1.创建 DiskFileItemFactory
DiskFileItemFactory factory = new DiskFileItemFactory();
2.创建ServletFileUpload类
ServletFileUpload upload=new ServletFileUpload(factory);
3.解析所有上传数据
List<FileItem> items = upload.parseRequest(request);
-
遍历items集合,集合中的每一项,就是一个上传数据.
-
编写代码:
upload3.jsp
<body> <form action="${pageContext.request.contextPath}/upload3" method="post" encType="multipart/form-data"> 普通文本框:<input type="text" name="content"><br> 文件上传:<input type="file" name="f" ><br> <input type="submit" value="提交" > </form> </body>
Upload3.java
@WebServlet("/upload3") public class Upload3 extends HttpServlet { private static final long serialVersionUID = 1L; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { ServletRequestContext context = new ServletRequestContext(request); // 1.创建 DiskFileItemFactory // DiskFileItemFactory factory = new DiskFileItemFactory();//使用默认的. File file = new File(this.getServletContext().getRealPath("/temp"));// 获取temp目录部署到tomcat后的绝对磁盘路径 DiskFileItemFactory factory = new DiskFileItemFactory(1024 * 1024, file);// 使用默认的. ServletFileUpload upload = new ServletFileUpload(factory); boolean flag = upload.isMultipartContent(context); // 用于判断是否是上传操作 if (flag) { // 解决上传文件名称中文乱码 upload.setHeaderEncoding("utf-8"); // 设置上传文件大小 // upload.setSizeMax(1024 * 1024 * 10);// 总大小为10m try { List<FileItem> items = upload.parseRequest(context); // 3.得到所有上传项 for (FileItem item : items) { if (item.isFormField()) { // 非上传组件 System.out.println("组件名称:" + item.getFieldName()); System.out.println("内容:" + item.getString("utf-8")); // 解决内容乱码问题 } else { // 上传组件 System.out.println("组件名称:" + item.getFieldName()); System.out.println("上传文件名称:" + item.getName()); String name = item.getName(); // 上传文件名称 System.out.println(name); name = name.substring(name.lastIndexOf("\\") + 1); IOUtils.copy(item.getInputStream(), new FileOutputStream("f:/upload/" + name)); // 工具类已经帮助我们把流进行关闭 // 删除临时文件 item.delete(); } } } catch (FileUploadException e) { // e.printStackTrace(); response.getWriter().write(e.getMessage()); return; } } else { response.setContentType("text/html;charset=utf-8"); response.getWriter().write("不是上传操作"); return; } } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
核心API介绍
-
DiskFileItemFactory
-
作用:可以设置缓存大小以及临时文件保存位置.
-
默认缓存大小是 10240(10k).
-
临时文件默认存储在系统的临时文件目录下.(可以在环境变量中查看)
-
new DiskFileItemFactory();
缓存大小与临时文件存储位置使用默认的. -
DiskFileItemFactory(int sizeThreshold, File repository)
sizeThreshold :缓存大小
repository:临时文件存储位置 -
对于无参数构造,也可以设置缓存大小以及临时文件存储位置.
setSizeThreshold(int sizeThreshold)
setRepository(File repository)
-
-
ServletFileUpload
-
ServletFileUpload upload=new ServletFileUpload(factory);
创建一个上传工具,指定使用缓存区与临时文件存储位置. -
List items=upload.parseRequest(request);
它是用于解析request对象,得到所有上传项.每一个FileItem就相当于一个上传项. -
boolean flag=upload.isMultipartContent(request);
用于判断是否是上传.
可以简单理解,就是判断encType=“multipart/form-data”; -
设置上传文件大小
void setFileSizeMax(long fileSizeMax) 设置单个文件上传大小
void setSizeMax(long sizeMax) 设置总文件上传大小 -
解决上传文件中文名称乱码
setHeaderEncoding(“utf-8”);
注意:如果使用reqeust.setCharacterEncoding(“utf-8”)也可以,但不建议使用。
-
-
FileItem
-
isFormField
用于判断是否是上传组件.
如果是返回的就是false,否则返回true. -
getFieldName();
返回值String,得到组件名称 name属性的值
-
getName();
返回值是String,得到的是上传文件的名称.
注意:浏览器不同,它们得到的效果不一样。
1.包含全路径名称 例如: C:\Users\Administrator\Desktop\a.txt
2.只包含上传文件名称 例如:a.txt -
getString();
这个方法可以获取非上传组件的内容,相当于 getParameter方法作用。
问题:如果信息是中文,会出现乱码,解决方案 getString(“utf-8”); 如果是上传组件,上传的文件是文本文件,可以获取到文件文件的内容。
但是如果不是文件文件,例如:是一张图片,这样获取合适吗? -
item.getInputStream();
获取上传文件的内容,保存到服务器端.
它是用于读取上传文件内容的输入流.
使用文件复制操作就可以完成文件上传。 -
item.delete();
删除临时文件
-
总结:关于文件上传时的乱码问题:
1.上传文件名称乱码
ServletFileUpload.setHeaderEncoding("utf-8");
2.非上传组件内容乱码
FileItem.getString("utf-8");
3.思考:上传文件信息是否会乱码,需要解决吗?
不需要解决,因为我们在上传时,使用的字节流来进行复制。
多文件上传
-
上传文件在服务器端保存位置问题
-
保存在可以被浏览器直接访问的位置
例如:商城的商品图片
保存在工程的WebRoot下的路径(不包含META-INF以及WEB-INF目录及其子目录) -
上传文件在服务器端保存位置问题
例如:付费的视频
1.工程中 META-INF WEB-INF目录及其子目录
2.不在工程中的服务器的磁盘目录下.
-
-
上传文件在同一个目录重名问题
-
在开发中解决这个问题,可以给上传文件起随机名称。
1.使用毫秒值
2.使用uuid
-
-
同一目录下文件过多
只需要分目录就可以.
1) 按照上传时间进行目录分离 (周、月 )
2) 按照上传用户进行目录分离 ----- 为每个用户建立单独目录
3) 按照固定数量进行目录分离 ------ 假设每个目录只能存放3000个文件 ,每当一个目录存满3000个文件后,创建一个新的目录
4)按照文件名的hashcode进行目录分离.
对同一目录下文件过多目录分离算法的讲解
根据文件名获取文件的hashCode值,并将其hashCode值转化为16进制,然后拆分成每一个,那么每一个都作为一个目录,一共可以都可以得到16个目录。每一层有可以有8个,总的就是16的8次方足够使用
在这里我们只创建2层,即16*16一共有256个目录。
public static String getRandomDirectory(String filename) {
//第一种写法
// // 获取文件名的hashCode值
// int hashcode = filename.hashCode();// 056d9363
// // 将其hashCode转化为16进制
// String hex = Integer.toHexString(hashcode);
//
// return "/" + hex.charAt(0) + "/" + hex.charAt(1);
//------------------------------------------------------------
//第二种写法
int hashcode = filename.hashCode();
// System.out.println(Integer.toBinaryString(hashcode));
Integer.toBinaryString(hashcode);
int a = hashcode & 0xf;
hashcode = hashcode >>> 4;
int b = hashcode & 0xf;
return "/" + a + "/" + b;
}
-
每次我们使用item.getName();获取的文件路径都是一个类似:C:\Users\Administrator\Desktop\a.txt为了我们使用只得到a.txt,我们将方法抽取出来
-
为了解决目录重名问题我们在上面获取到的真是文件名,将其改装成随机的文件名,并抽取出方法
-
抽取目录分离算法
FileLoadUtils.java
package com.syj.Utils;
import java.util.UUID;
public class FileLoadUtils {
// 得到上传文件真实名称 c:\a.txt 或者 a.txt
public static String getRealName(String name) {
// 从字符串后面数得到最后一个\的加1位置
int len = name.lastIndexOf("\\") + 1;
// System.out.println(len);
return name.substring(len);
}
// 获取随机名称 a.txt
public static String getUUIDFileName(String filename) {
int len = filename.lastIndexOf(".");
if (len != -1) {
return UUID.randomUUID() + filename.substring(len);
} else {
// 有的文件没有后缀名,我们直接返回随机名称即可
return UUID.randomUUID().toString();
}
}
// 目录分离算法
public static String getRandomDirectory(String filename) {
// // 获取文件名的hashCode值
// int hashcode = filename.hashCode();// 056d9363
// // 将其hashCode转化为16进制
// String hex = Integer.toHexString(hashcode);
//
// return "/" + hex.charAt(0) + "/" + hex.charAt(1);
//------------------------------------------------------------------
int hashcode = filename.hashCode();
// System.out.println(Integer.toBinaryString(hashcode));
Integer.toBinaryString(hashcode);
int a = hashcode & 0xf;
hashcode = hashcode >>> 4;
int b = hashcode & 0xf;
return "/" + a + "/" + b;
}
//用来测试方法
public static void main(String[] args) {
// String path = getRandomDirectory("a.txt");
// File file = new File("F:/upload");
// File directory = new File(file, path);// 在父目录下面创建子目录
// if (!directory.exists()) {
// directory.mkdirs();
// }
getRealName("c:\\a.txt");
getRealName("a.txt");
}
}
实现完整的文件上传案例:
upload4.jsp使用js不断的添加文件上传的组件实现对文件上传
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>多文件上传</title>
<!-- js对文件上传 -->
<script type="text/javascript">
function addFile() {
var div = document.getElementById("content");
div.innerHTML += "<div><input type='file' name='f'><input type='button' value='remove file' 'removeFile(this)'></div>";
}
function removeFile(btn){
document.getElementById("content").removeChild(btn.parentNode);
}
</script>
</head>
<body>
<input type="button" value="add File" onclick="addFile();" />
<form action="${pageContext.request.contextPath}/upload4" method="post" encType="multipart/form-data">
普通文本框:<input type="text" name="content"><br> 文件上传:<input
type="file" name="f"><br>
<div id="content"></div>
<input type="submit" value="提交">
</form>
</body>
</html>
Upload4.java核心代码完整的文件上传
package com.syj.Web.Servlet;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
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.tomcat.util.http.fileupload.FileItem;
import org.apache.tomcat.util.http.fileupload.FileUploadException;
import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.apache.tomcat.util.http.fileupload.disk.DiskFileItemFactory;
import org.apache.tomcat.util.http.fileupload.servlet.ServletFileUpload;
import org.apache.tomcat.util.http.fileupload.servlet.ServletRequestContext;
import com.syj.Utils.FileLoadUtils;
@WebServlet("/upload4")
public class Upload4 extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
ServletRequestContext context = new ServletRequestContext(request);
response.setContentType("text/html;charset=utf-8");
// 1.创建 DiskFileItemFactory
// DiskFileItemFactory factory = new DiskFileItemFactory();//使用默认的.
File file = new File(this.getServletContext().getRealPath("/temp"));// 获取temp目录部署到tomcat后的绝对磁盘路径
DiskFileItemFactory factory = new DiskFileItemFactory(1024 * 1024, file);// 使用默认的.
// 2.创建ServletFileUpload
ServletFileUpload upload = new ServletFileUpload(factory);
boolean flag = upload.isMultipartContent(context); // 用于判断是否是上传操作
if (flag) {
// 解决上传文件名称中文乱码
upload.setHeaderEncoding("utf-8");
// 设置上传文件大小
// upload.setSizeMax(1024 * 1024 * 10);// 总大小为10m
try {
List<FileItem> items = upload.parseRequest(context);
// 3.得到所有上传项
for (FileItem item : items) {
if (!item.isFormField()) {
// 上传文件名称
String name = item.getName();
// 得到上传文件的真实名称
String filename = FileLoadUtils.getRealName(name);
// 给文件一个随机名称
String uuidname = FileLoadUtils.getUUIDFileName(filename);
// 得到随机目录
String randomDirectory = FileLoadUtils.getRandomDirectory(filename);
// 1.假如上传到本地,比如F:/upload
// File rd = new File("F:/upload", randomDirectory);
// 1.假如到项目目录下,可访问
String parentPath = this.getServletContext().getRealPath("/upload");
File rd = new File(parentPath, randomDirectory);
// 注意随机目录可能不存在需要自己创建
if (!rd.exists()) {
rd.mkdirs();
}
IOUtils.copy(item.getInputStream(), new FileOutputStream(new File(rd, uuidname)));
// 工具类已经帮助我们把流进行关闭
// 删除临时文件
item.delete();
}
}
} catch (FileUploadException e) {
// e.printStackTrace();
response.getWriter().write(e.getMessage());
return;
}
} else {
response.getWriter().write("不是上传操作");
return;
}
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
发现的问题:
- 发现原来的parseRequest(reqeust)方法已经不建议使用了,需要换成RequestContext才行
- 代码中推荐使用RequestContext
- 原来的parseRequest(HttpServletRequest req)已经不推荐使用
- 那么RequestContext好像没有见到过,又该如何获取?我们应该养成自学的习惯,有一种思维
解决问题:
- 发现RequestContext是一个接口。
- 我们可以查看他的实现接口和实现类
- 分别查看:
- 对于ServletRequestContext我们比较熟悉
-
至此问题基本得到解决
体现在代码中:
ServletRequestContext context = new ServletRequestContext(request);//第26行 List<FileItem> items = upload.parseRequest(context);//第41行