# java实现文件上传下载
# 技术
文件上传可以使用的技术:
JSPSmartUpload:应用在jsp上的文件上传和下载的组件
FileUpload:应用在java环境上的文件的上传功能
Servlet3.0:提供文件上传的功能
Struct2:提供文件上传的功能
# 要素
要将文件上传,需要满足三个要素
表单提交的方式需要使post
get请求会长度有限制,post请求长度没有限制
表单中需要有
<input type="file">
元素,而且还需要有name
属性和值表单
enctype="multipart/from-data"
# 文件上传原理分析
# 表单enctype="multipart/from-data"
表单enctype="multipart/from-data"
不是这个值,默认情况下,上传文件请求体中的数据形式
<form action="" enctype="application/x-www-form-urlencoded" method="post"> <span>文件描述:</span><input type="text" name="file_descript"><br> <span>文件路径:</span><input type="file" name="fil"><br> <button type="submit">上传</button> </form>
1
2
3
4
5
请求体内容
file_descript=aaa&file=fileupload.txt
如果
enctype="application/x-www-form-urlencoded"
为默认,那么获取上传文件时,不能获取到文件的内容,只能获取到上传文件的名称
当enctype="multipart/from-data"为这个的时候,进行抓包分析
boundary后的值是分割线,在火狐的请求中可以看到数据
请求体中的数据都是以boundary
值作为分割开的
# 开始
# 步骤
导包
创建磁盘文件项工厂
Dis
利用核心类解析request,解析后得到多个部分,返回一个list集合,list集合中存储了每个部分的内容(Fileitem文件项)
遍历list集合,会得到代表每个部分的文件项的对象,根据文件项判断是否是文件上传项
写入到磁盘中
# 代码演示
@Override
protected void doPost (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
try {
//1.创建磁盘文件项工厂
DiskFileItemFactory factory = new DiskFileItemFactory();
//2.创建核心解析类
ServletFileUpload fileUpload = new ServletFileUpload(factory);
//3.利用核心解析类request,解析后得到多个部分,返回一个list集合
List<FileItem> fileItems = fileUpload.parseRequest(request);
//4. 遍历fileitems
for (FileItem fileItem : fileItems) {
//判断这个文件项是否是普通项还是文件上传项
if (fileItem.isFormField()) {
//普通项
//接收普通项的值,不能使用request.getParameter()
//获取名称
String name = fileItem.getFieldName();
//获取值 如果是中文的话,使用有参方法,参数传递编码方式
String value = fileItem.getString();
System.out.println("普通项: "+name+"-->"+value);
}else {
//是文件项
//获取文件上传项的名称
String name = fileItem.getName();
System.out.println("文件项名称:"+name);
//获取文件中的数据
InputStream is = fileItem.getInputStream();
//读取输入流并将其写入到磁盘中
//获取真实路径
String realPath = request.getServletContext().getRealPath("/upload");
System.out.println(realPath);
FileOutputStream os = new FileOutputStream(new File(realPath+"/"+name),true);
byte[] bytes = new byte[1024*1024];
int read = 0;
while ((read = is.read(bytes)) != -1) {
os.write(bytes,0,read);
}
os.close();
is.close();
}
}
} catch (FileUploadException e) {
e.printStackTrace();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
必须要保证在request.getServletContext().getRealPath("/upload");的upload文件夹中存在至少一个文件,如果这个文件夹中为空,那么不会自动在out目录中创建这个文件夹
# DiskFileItemFactory类
构造方法
public DiskFileItemFactory() { this(10240, (File)null); } public DiskFileItemFactory(int sizeThreshold, File repository) { this.sizeThreshold = 10240; this.defaultCharset = "ISO-8859-1"; this.sizeThreshold = sizeThreshold; this.repository = repository; }
1
2
3
4
5
6
7
8
9
10
File repository
设置临时文件存放的位置
int sizeThreshold
是设置缓冲区的大小,默认是10kb,如果上传的文件大于10kb的话,那么就会为这个文件创建一个临时文件,存放的位置为File repository
,常见临时文件的好处:可以为程序提供连续写入
此文件就是一个临时文件,其大小和原文件的大小一样
可以调用setRepository(File repository)
设置临时文件的存放路径,setSizeThreshold(int sizeThreshold)
设置缓冲区的大小
# ServletFileUpload类的使用
方法介绍
public static final boolean isMultipartContent(HttpServletRequest request)
静态,返回是否form表单中的是否为enctype="multipart/form-data"
Map<String,List<FileItem>> parseParameterMap(HttpServletRequest request)
解析请求体,返回一个map集合List<FileItem> parseRequest(HttpServletRequest request)
解析request,返回一个list集合setFileSizeMax(long fileSizeMax)
设置单个文件的最大,如果某个上传文件超过这个大小,那么就会报错setHeaderEncoding(String encoding)
处理中文乱码的情况,设置字符集编码,也就是上传文件名为中文的情况setSizeMax(long sizeMax)
设置上传文件或者文件夹总共的大小setProgressListener(ProgressListener pListener)
设置文件的监听器,可以通过这个获取文件上传的进度
# FileItem类
这个类是ServletFileUpload
对象调用parseRequest(request)
获取的,没有构造方法
返回一个list集合,每一个FileItem
对象就是Boundsay
分隔开中的值
上图返回的FileItem
对象为:
name=null, StoreLocation=null, size=0 bytes, isFormField=true, FieldName=file_descript
name=fileupload.txt, StoreLocation=null, size=18 bytes, isFormField=false, FieldName=file_path
2
isFormField()
判断每一个fileitem项是不是普通项,如果是,返回true,否则返回falsegetContentType()
返回此字段项的MEMI的文件类型,如果是文本<input type="text" name="file_descript">
返回nullgetFieldName()
获取文件项或者普通项的name
字段的名称getName()
获取文件项的上传文件名,否则返回nullgetString("utf-8")
获取文件项上传文件的内容,普通项返回空
# 多文件的上传
如果一次性想要上传多个文件,那么可以多创建几个type="file",fileitem获取到的文件项就是添加的个数
# ie或者更低版本浏览器获取文件名的问题
如果使用ie或者是更低的浏览器进行文件的上传,会遇到获取文件名是这种格式的C:\Users\chuchen\Documents\璐﹀彿淇℃伅鏂囨。\fileupload.txt
,带着路径
解决方式
for (FileItem fileItem : fileItems) {
String name = fileItem.getName();
int i = name.lastIndexOf("\\");//lastIndexOf返回此字符串最后一次出现时的索引,如果找不到,发挥-1
System.out.println(i);
if (i != -1) {
//是低版本浏览器
name = name.substring(i+1);
}
System.out.println(name);
}
2
3
4
5
6
7
8
9
10
11
12
# 关于上传至同一个文件夹,出现同名问题解决
如果有两个用户,都上传a.txt
都同一个文件夹中,那么后面上传的那个的文件,将会替换掉原来最开始上传的那个文件,这是不允许的,可以使用几种方式进行解决
UUID uuid = UUID.randomUUID();
System.out.println(uuid);
System.out.println(uuid.toString());
System.out.println(uuid.toString().replace("-", ""));
2
3
4
UUID
类的randomUUID()
方法可以随机的获取一个字符串形式串,唯一,可以将用户上传的文件,使用这个UUID重新命名,这样就解决了重名的情况
f472f687-efe7-4e3d-995a-e922da7359d0
f472f687-efe7-4e3d-995a-e922da7359d0
f472f687efe74e3d995ae922da7359d0
2
3
# 目录分离分析
如果用户将上传的文件,全部保存在一个文件夹中,那么就会出现打开的时候,非常卡的情况(如果该目录中的文件,足够多),所以应该合理的分配文件的位置
# 目录分离
- 按时间分离:按月,周,天,小时
- 按用户分离:每一个用户自己有一个文件夹
- 按个数分离:只要某个目录中的文件的总个数满足多少个,那么就重新自动创建一个新的目录,但是这样的话,会使程序的运行速度变低,因为每一次上传,后台都会计算当前目录中的文件的个数
- 按目录分离算法:这是最高级的分离方式,按照某种算法对用户上传的文件进行分离
算法目录分离分析
- 上传一个文件,通过UUID得到一个唯一的新文件名,使用这个
新文件名.hasCode()
得到一个hascode值,int类型是32位,- 让这个hascode值
& 0xf
返回的这个值作为一级目录,- 然后让hascode右移4位,然后在
& 0xf
的值作为二级目录- 以此类推
程序算法
package com.chu.util;
import java.util.UUID;
public class PathUtil {
//此类用于返回目录分离的算法路径
private static String getUUID() {
return UUID.randomUUID().toString().replace("-","");
}
public static String UUID_path(String fileName) {
int i = fileName.lastIndexOf(".");
return getUUID()+fileName.substring(i);
}
public static String getRealPath(String UUID_path) {
//通过唯一文件名获取hascode值
int code1 = UUID_path("sd.txt").hashCode();
//code1 & 0xf d1为一级目录
int d1 = code1 & 0xf;
//>>>为无符号右移
int code2 = code1 >>> 4;
//d2为二级目录
int d2 = code2 & 0xf;
return "/"+d1+"/"+d2;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26





