文件上传概述
1.文件上传是什么
在web开发中经常需要从客户端向服务器上传文件,如:上传照片,上传新闻图片,上传附件等等,这些都需要通过web开发中的文件上传技术实现.
2.文件上传步骤
实现wb开发中的文件上传功能只需要两个步骤:
(1).提供一个带有文件上传项的表单.
(2).在servlet中读取处理上传的文件,保存到服务器.
文件上传实现
1.在用户页面中添加上传输入项(客户端页面操作)
<input type="file" name="filex">
注意事项:
(1).必须为文件上传input提供name属性,否则文件上传内容不会被表单提交.
(2).表单的提交方式必须为post(get提交数据在url地址上显示,有长度限制)
(3).表单必须设置enctype=multipart/form-data
2.在服务器端编写文件上传程序
通过Request对象提供一个getInputStream方法,可以读取到客户端提交过来的数据,但这种方式还需要对流中获取到的数据进行处理.
为了简化这个处理过程,Apache开源组织提供了一个用来处理表单文件上传的一个开源组件(Commons-fileupload),让开发人员轻松实现web文件上传功能.
3.上传组件(Apache commons-fileupload)使用过程
首先需要下载并导入该组件相关的支撑jar包:Commons-filesupload和commons-io,然后编程实现,步骤如下:
(1)创建DiskFileItemFactory对象,设置缓冲区大小和临时文件目录
public DiskFileItemFactory(int sizeThreshold,java.io.File repository)
//构造工厂时,指定内存缓冲区大小和临时文件存放位置
public void setSizeThreshold(int sizeThreshold)
//设置内存缓冲区大小,默认10k
public void setRepository(java.io.File repository)
//设置临时文件存放位置,默认 System.getProperty("java.io.tmpdir")
*内存缓存区:上传文件时,上传文件的内容优先保存在内存缓冲区中,当
*上传文临时大小超过缓冲区大小,就会在服务器端产生临时文件
*临时文件存放位置:保存超过了内存缓冲区大小上传文件而产生临时文件
*产生临时文件可以通过FileItem的delete方法删除
(2).使用DiskFileItemFactory对象创建ServletFileUpload对象,并设置上传文件的大小限制.
(3).调用ServletFileUpload.parseRequest方法解析request对象,得到一个保存了所有上传FileItem的list集合
ServeltFIleUpload负责处理上传的文件数据,并将表单中每个输入项封装成一个FileItem对象中.常用的方法有:
//判断上传表单是否为multipart/form-data类型
boolean isMutipartContent(HttpServletRequest request)
//解析request对象,并把表单中的每一个输入项包装成fileItem对象,并返回一个保存了所有FileItem的list集合.
List parseRequest(HttpServletRequest request)
//设置单个上传文件的最大值
setFileSizeMax(long fileSizeMax)
//设置上传文件总量的最大值
setSizeMax(long sizeMax)
//解决上传文件名乱码问题
setHeadEncoding(java.lang.String encoding)
//实时监听文件上传状态
setProgressListener(ProgressListener pListener)
(4).对List进行迭代,每迭代一个FileItem对象,调用其isFormField方法判断是否上传文件.
True为普通表单字段,则调用getFieldName,getString方法得到字段名和字段值.
False为上传文件项,则调用getInputStream方法得到数据输入流,从而读取上传数据.
(5).如果是文件上传就通过流获取上传的数据保存到服务器中.
boolean idFormField()判断FileItem是一个文件上传对象还是普通字段项:
String getFieldName() 获得普通表单对象的name属性
String getString(String encoding)获得普通表单对象的value属性,可以用encoding进行编码设置
文件上传项:
String getName()获得上传文件的文件名(有些浏览器会携带客户端路径)
InputStream getInputStream()获得上传文件的输入流
delete() 在关闭FileItem输入流后,删除临时文件
上传文件的监听
//ServletFileUpload类提供了如下方法,监听文件上传时的进度信息:
pulbic void setProgressListener(ProgressListener pListener)
//设置监听器,文件上传程序会自动执行,监听器中update方法
public void update(long pBytesRead,long pContentLength,int pItems)
//在方法中可以获得文件总大小,已经上传大小和上传第几个元素
能否根据上面三个参数计算:剩余大小、传输速度、已用时间、剩余时间
(1).已用时间 = 当前时间 -开始时间
(2).速度 = 已经上传大小/已用时间
(3).剩余时间 = 总大小 - 已经上传大小
(4).剩余时间 = 剩余大小/速度
上传文件注意问题
1.文件保存位置
上传的文件如果直接存放在web应用根目录下,通过浏览器是可以直接访问的,如果用户上传一个JSP,再通过浏览器访问就可以通过文件上传在服务器执行任意的java代码,十分危险.
所以一定要注意,文件上传服务器保存文件的位置必须是WEB-INF目录下或服务器中浏览器无法访问的位置.
2.防止上传的文件重名覆盖
上传的文件如果重名,后上传的文件就会覆盖先上传文件. 为了防止这样的问题产生,应该对文件名进行处理,可以在文件名钱拼接UUID,防止文件名重复.
filename = UUID.randomUUID().toString()+"_"+filename;
3.防止同一个目录下上传文件过多
一个目录下如果文件过多,必须会导致访问效率下降,所以我们需要将上传的文件进行分目录存储.
分目录存储的算法可以有很多:
(1).按照上传时间进行目录分离(周,月)
(2).按照上传用户进行目录分离—为每个用户建立单独目录
(3).按照固定数量进行目录分离–假设每个目录只能存在3000个文件后,创建一个新的目录
(4).按照唯一文件名的hashcode进行目录分离
public static String generateRandomDir(String uui获dFileName){
//获得唯一文件名的hashcode
int hashcode = uuidFileName.hashCode();
//获得一级目录
int d1 = hashcode & 0xf;
//获得二级目录
int d2 = (hashcode >>> 4)&0xf;
return "/"+ d2 + "/"+ d1;//共有256目录
}
4.文件上传代码
1.创建一个UploadServlet类
try {
response.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=utf-8");
//1.创建文件上传工厂
DiskFileItemFactory factory = new DiskFileItemFactory(100, new File(this.getServletContext().getRealPath("WEB-INF/temp")));
//2.创建文件上传的核心类
ServletFileUpload fileUpload = new ServletFileUpload(factory);
//--判断当前文件上传的表单是否满足enctype=multipart/form-data
if(!fileUpload.isMultipartContent(request)){
throw new RuntimeException("请使用正确的文件上传表单上传数据!");
}
//--设置文件名解析时采用的编码
fileUpload.setHeaderEncoding("utf-8");
//--单个文件不能超过1MB
fileUpload.setFileSizeMax(1024 * 1024);
//--总大小不能超过10MB
fileUpload.setSizeMax(10 * 1024 * 1024);
//--设置上传文件的监听
fileUpload.setProgressListener(new ProgressListener(){
long begin = System.currentTimeMillis();
public void update(long pBytesRead, long pContentLength,int pItems) {
System.out.print("正在读取第"+pItems+"个上传项。。");
System.out.print("共"+pContentLength+"字节。。");
System.out.print("已经读取了"+pBytesRead+"字节。。");
long leftBytes = pContentLength - pBytesRead;
System.out.print("剩余"+leftBytes+"字节。。");
long now = System.currentTimeMillis();
long usetime = (now - begin)/1000 ;
System.out.print("已经用时" + usetime+"秒。。");
long speed = usetime == 0 ? 0 : pBytesRead / usetime / 1024;
System.out.print("上传速度" + speed+"KB/s。。");
double per = Math.round(pBytesRead * 10000.0 / pContentLength)/100.0;
System.out.println("上传百分比" + per + "%。。");
long lefttime = speed == 0 ? 0 : leftBytes /1024 / speed;
System.out.println("大致剩余时间"+lefttime+"秒");
request.getSession().setAttribute("progress", per);
}
});
//--解析request得到FileItem的集合
List<FileItem> items = fileUpload.parseRequest(request);
//--遍历每个item分别做处理
for(FileItem item : items){
if(item.isFormField()){
//普通字段项
String name = item.getFieldName();
String value = item.getString("utf-8");
System.out.println(name+"~"+value);
}else{
//文件上传项
String fname = item.getName();
//--处理ie文件名bug
if(fname.contains("\\")){
fname = fname.substring(fname.lastIndexOf("\\")+1);
}
//--处理文件名 使其不会重复
String savename = UUID.randomUUID().toString()+"_"+fname;
//--文件分目录处理
//----获取文件名的hash 转换为16进制字符串表现形式 由于文件名随机 所以 hash值也是散列的
String hash = Integer.toHexString(savename.hashCode());
//----如果hash不足8位则在前面拼接足够8位的0
while(hash.length()<8){
hash += "0";
}
//----遍历hash值字符串的每一个字符作为一级目录拼接
String savepath = "/WEB-INF/upload/";
for(int i = 0;i<hash.length();i++){
savepath += (hash.charAt(i)+"/");
}
//----创建出该目录
new File(this.getServletContext().getRealPath(savepath)).mkdirs();
//----得到输入流
InputStream in = item.getInputStream();
//----得到输出流 路径就是上面 hash拼接出的路径+文件名
OutputStream out = new FileOutputStream(this.getServletContext().getRealPath(savepath + "/" +savename));
//----输出数据到文件
byte [] data = new byte[1024];
int n = -1;
while((n = in.read(data))!=-1){
out.write(data,0,n);
}
//----关闭流
in.close();
out.close();
//----删除缓存文件
item.delete();
}
}
} catch (FileSizeLimitExceededException e) {
response.getWriter().write("文件大小超过限制!!");
} catch (Exception e) {
throw new RuntimeException(e);
}
关注下面微信公众号获得更多学习资源!