10月1假期圆满结束,在家里躺了5天,啥也没干,回来的时候家里还给我拿了螃蟹什么的,昨天就让我吃了,哈哈哈。
这几天一直想实现以下文件的上传和文件上传时显示进度条,看了几个博客,照着自己敲了一下,将自己不明白的地方也查了百度。但仍然有几处不太理解,我会将不明白的地方在本文的下方写出,希望大家能帮我解答一下。
下面就是实现的过程。
我们后台使用的是servlet来处理用户请求,那么就要在web.xml文件中配置我们的servlet。在表单提交时,我们可以在action中填写对应的setvlet映射名,就可以将表单数据发送到servlet中。
UploadServlet
com.java.controller.UploadServlet
UploadServlet
/UploadServlet.do
后台使用servlet,那么我们就需要servlet-api.jar,并且由DiskFileItemFactory,我们还需另外两个jar包。所需要的3个jar包如下。
这个servlet就是用来处理用户上传文件的请求的,代码如下:
package com.java.controller;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.List;
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.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.FileUtils;
public class UploadServlet extends HttpServlet {
/*这个class是一个servlet,那么就能够通过web.xml文件中配置的映射路径访问。
*访问的时候按照请求的方式,来决定用哪一个方法来处理请求
* */
//重写doGet()方法和doPost()方法。
@Override
public void doGet(HttpServletRequest request,HttpServletResponse response) {
}
@Override
public void doPost(HttpServletRequest request,HttpServletResponse response) throws IOException {
//首先检查表单是否支持文件上传,检查请求头Content-type:multipart/form-data。
if(!ServletFileUpload.isMultipartContent(request)) {
throw new RuntimeException("该表单不支持文件上传");
}
/*设置响应体属性
response.setContentType("text/html;charset=UTF-8");
*/
//创建核心工厂
DiskFileItemFactory diskFileItemFactory=new DiskFileItemFactory();
//为这个制造上传文件对象的工厂设置属性。
diskFileItemFactory.setSizeThreshold(1024*3); //设置工厂的内存缓冲区大小,默认为10kb。
diskFileItemFactory.setRepository(new File("E:\\FileUpLoad")); //设置工厂的临时文件目录:当上传文件大于内存缓冲区时,使用临时文件目录缓存上传的文件。
//通过工厂创建文件上传解析器,核心类
ServletFileUpload servletFileUpload=new ServletFileUpload(diskFileItemFactory);
//为这个上传解析器设置属性
servletFileUpload.setSizeMax(1024*1024*2); //设置上传数据的最大值。所有文件。
servletFileUpload.setFileSizeMax(1024*1024*2); //设置上传文件的最大值。单个文件。
servletFileUpload.setHeaderEncoding("UTF-8"); //在内存中保存的数据是以某种字符集编码的字节数组?
//设置文件上传的监听器
servletFileUpload.setProgressListener(new MyProgressListener(request)); //要在数据解析之前进行监听?
//进行数据的解析。将表单中每个输入项(包括表单普通标签和表单文件上传标签)封装成一个FileItem对象。
try {
List fileItemList=servletFileUpload.parseRequest(request);
for(FileItem fileItem:fileItemList) {
//判断是表单普通标签,还是表单文件上传标签。
if(fileItem.isFormField()) { //表单普通标签
String fieldName=fileItem.getFieldName(); //得到标签的name名。
String fieldValue=fileItem.getString("utf-8"); //得到标签的value值。getString()用于解决乱码问题?
System.out.println("标签名为:"+fieldName+",value值为:"+fieldValue);
}else { //为表单的文件上传标签
String fileName=fileItem.getName(); //得到上传文件的文件名。IE浏览器的得到的是全路径 : C:\Users\xxx\Desktop\abc.txt ; 其他浏览器得到的只是文件名 : abc.txt
String fieldName=fileItem.getFieldName();
String fieldValue=fileItem.getString("utf-8"); //如果要打印文件值,那么就会在控制台中打印乱码(许多)。
System.out.println("标签名为:"+fieldName+",value值为:"+",文件名为:"+fileName);
//进行文件的上传(将文件保存到服务器中【文件的拷贝】)。
InputStream inputStream=fileItem.getInputStream(); //这个输入流是从哪里输入?
String parentDir=this.getServletContext().getRealPath("/WEB-INF/upload/"+fileName); //文件要保存的目的目录。和getServletContextPath()有什么区别?ServletContext()是什么?
String textDir=this.getServletContext().getContextPath();
System.out.println("RealPath:"+parentDir+",ContextPath:"+textDir);
//进行文件的保存(复制)
File file=new File(parentDir);
FileUtils.copyInputStreamToFile(inputStream, file); //在保存的时候,如果文件夹和文件不存在,则创建文件夹和文件。
//删除临时文件
//fileItem.delete();
//关闭流资源
inputStream.close();
/*图片的大小和占用空间的区别
*b,kb等的转换。
*
*
* */
}
}
} catch (FileUploadException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
这个class是一个servlet,那么就能够通过web.xml文件中配置的映射路径访问。访问的时候按照请求的方式,来决定用哪一个方法来处理请求 ,我们需要重写其中的doGet()方法和doPost()方法。
在doPost()方法中,我们首先检查表单是否支持文件上传,检查请求头Content-type:multipart/form-data。如果是则支持上传。
然后我们创建核心工厂类DiskFileItemFactory,并给它设置一些基本属性,比如内存缓冲大小和指定临时文件。
通过核心工厂类我们能够得到与之相关联的核心类ServletFileUpload。当然了,我们也要给这个核心类设置一些属性,例如上传文件的总共大小,单个文件上传的总共大小。如果所需上传文件数据大于最大值,那么就会发生异常。
设置完核心工厂类,我们就可以设置进度监听。
监听完成后,说明文件下载完成,我们就可以对表单提交过来各个数据进行解析。我们解析传递过来的request请求,解析完成后会得到一个FileItem集合。在这里,我们可以简单的理解为这个FileItem集合就是表单中所有表单的集合。我们通过 遍历这个集合就可以得到每个标签的name值,value值等信心。
我们只对文件类型的信息感兴趣,所以我们只需要关注表单上传文件标签type=file。所以呢,在遍历的时候,我们通过fileItem.isFormField()方法来辨别出一个标签是表单普通标签,还是表单上传文件标签。如果是上传文件标签,那么那么我们可以的到这个标签的name值,和上传文件的文件名。这里需要注意,这个标签的value代表的应该是文件本身,因为我打印value的时候,在控制台输出了很多乱码,乱码代表的应该就是文件。
我们通过getRealPath()来得到项目相对于服务器的实际路径,而getContextPath()得到的是项目的相对路径,也就是项目名本身。
最后我们通过FileUtils.copyInputStreamToFile(inputStream, file)方法来将文件拷贝到指定地方。
<%@ page contentType="text/html;charset=UTF-8" %>
上传文件
假如我们要以表单的形式进行文件上传,那么表单的提交方式必须为post,并且要设置表单的 enctype 属性值为 multipart/form-data。
上传文件时,表单提交要使用post方式,因为这种请求是放到httpbody中,传输的文件无限制。
只有使用enctype="multipart/form-data",表单才会把文件的内容编码到HTML请求中,否则只会把所要上传的文件名放到httpbody,而不是把文件内容编码后放到httpbody中。
文件的上传:我们把需要上传的资源,发送给服务器,在服务器上保存下来。
这个文件就是从来监听文件上传的进度的。
package com.java.controller;
import org.apache.commons.fileupload.ProgressListener;
import java.text.NumberFormat;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.apache.commons.fileupload.ProgressListener;
public class MyProgressListener implements ProgressListener {
//定义session,扩大request作用域为session。
private HttpSession httpSession;
public MyProgressListener(HttpServletRequest request) {
httpSession=request.getSession();
}
/**
* arg0表示已经读到的内容,
* arg1表示需要读取的数据总数,
* arg2表示读取当前的列表项
*/
@Override
public void update(long arg0, long arg1, int arg2) {
//进行数据的格式化,将字节B转换为MB。
double read=arg0/1024.0/1024.0;
double totle=arg1/1024.0/1024.0;
double percent=read/totle; //以读取的%比。
String read_format=dataFormat(read);
String total_format=dataFormat(totle);
NumberFormat numberFormat=NumberFormat.getPercentInstance();
String percentReault=numberFormat.format(percent);
//将信息存入session
httpSession.setAttribute("progress", percentReault);
System.out.println("已读:"+arg0+"----总数:"+arg1+"----当前Item:"+arg2);
System.out.println("已读:"+read+"MB"+"----总数:"+totle+"MB"+"----读取进度:"+percent+"%");
System.out.println("已读:"+read_format+"----总数:"+total_format+"----读取进度:"+percentReault);
System.out.println("------------------------");
}
public String dataFormat(double data){
//进行数据的个格式化,
String formdata="";
if (data>=1024*1024) { //大于等于1M
formdata=Double.toString(data/1024/1024)+"M";
}else if(data>=1024){//大于等于1KB
formdata=Double.toString(data/1024)+"KB";
}else{//小于1KB
formdata=Double.toString(data)+"byte";
}
return formdata.substring(0, formdata.indexOf(".")+2); //取小数点后两位。
/*double类型的数据有几个小数点。
*该方法中的其他方法的作用。
* */
}
}
通过打印到控制台的数据,我们可以看到update()方法被指定了很多次。并且上述中有java.lang.Double.toString() 方法,作用是返回此Double对象的字符串表示形式。而且返回的不是小数点后2位,而是小数点后一位(注释写错了)。
打印到控制台部分数据:
已读:806162----总数:827192----当前Item:4
已读:0.7688159942626953MB----总数:0.7888717651367188MB----读取进度:0.9745766400061896%
已读:0.7----总数:0.7----读取进度:97%
------------------------
已读:810216----总数:827192----当前Item:4
已读:0.7726821899414062MB----总数:0.7888717651367188MB----读取进度:0.9794775578100369%
已读:0.7----总数:0.7----读取进度:98%
------------------------
已读:810300----总数:827192----当前Item:4
已读:0.7727622985839844MB----总数:0.7888717651367188MB----读取进度:0.9795791061809108%
已读:0.7----总数:0.7----读取进度:98%
------------------------
已读:814354----总数:827192----当前Item:4
已读:0.7766284942626953MB----总数:0.7888717651367188MB----读取进度:0.9844800239847581%
已读:0.7----总数:0.7----读取进度:98%
------------------------
已读:818408----总数:827192----当前Item:4
已读:0.7804946899414062MB----总数:0.7888717651367188MB----读取进度:0.9893809417886053%
已读:0.7----总数:0.7----读取进度:99%
------------------------
已读:818492----总数:827192----当前Item:4
已读:0.7805747985839844MB----总数:0.7888717651367188MB----读取进度:0.9894824901594793%
已读:0.7----总数:0.7----读取进度:99%
------------------------
已读:822546----总数:827192----当前Item:4
已读:0.7844409942626953MB----总数:0.7888717651367188MB----读取进度:0.9943834079633266%
已读:0.7----总数:0.7----读取进度:99%
------------------------
已读:826600----总数:827192----当前Item:4
已读:0.7883071899414062MB----总数:0.7888717651367188MB----读取进度:0.9992843257671737%
已读:0.7----总数:0.7----读取进度:100%
------------------------
已读:826684----总数:827192----当前Item:4
已读:0.7883872985839844MB----总数:0.7888717651367188MB----读取进度:0.9993858741380478%
已读:0.7----总数:0.7----读取进度:100%
------------------------
已读:827192----总数:827192----当前Item:4
已读:0.7888717651367188MB----总数:0.7888717651367188MB----读取进度:1.0%
已读:0.7----总数:0.7----读取进度:100%
------------------------
标签名为:name,value值为:qiao
标签名为:namex,value值为:qiaox
标签名为:selectAge,value值为:24
标签名为:fileUpLoad,value值为:,文件名为:壁纸.png
RealPath:F:\Development\DevelopmentTool\apache-tomcat-9.0.4-windows-x64\apache-tomcat-9.0.4\webapps\FileUploadAndProgressBar\WEB-INF\upload\壁纸.png,ContextPath:/FileUploadAndProgressBar
在给工厂设置内存缓存时,如果文件大小大于内存缓存,是否将文件全部缓存到临时文件中?还是将内存缓存占满后,其它的数据在放到临时文件中?
设置相应体的属性 response.setContentType("text/html;charset=UTF-8");的意义和作用是什么?
给文件解析器设置servletFileUpload.setHeaderEncoding("UTF-8");的意义和作用是什么?
进度监听是否需要在解析request请求之前进行监听?这个我猜是,因为打印的时候先把文件下载完才进行解析的。
InputStream inputStream=fileItem.getInputStream();这句代码是从哪里获得输入的,文件在复制的过程中,是怎样进行流的转换的,流是从哪里去了哪里?