文章目录
一、文件上传
1.1 文件上传对页面的要求
- 必须使用form表单,而不能是超链接;
- 表单的method必须是POST,而不能是GET;
- 表单的enctype必须是multipart/form-data;
- 在表单中添加file表单字段,即<input type=”file”…/>
如下为一个简单的文件上传页面:
<form action="${pageContext.request.contextPath }/FileUploadServlet" method="post" enctype="multipart/form-data">
用户名:<input type="text" name="username"/><br/>
文件1:<input type="file" name="file1"/><br/>
文件2:<input type="file" name="file2"/><br/>
<input type="submit" value="提交"/>
</form>
1.2 文件上传表单和普通文本表单的区别
- 文件上传表单:
请求方式只能是post,且需要设置form表单的属性enctype=”multipart/form-data”,含义为:多部件表单数据; - 普通文本表单:
可以不设enctype属性,有默认值
当method=”post”时,enctype的默认值为application/x-www-form-urlencoded,表示使用url编码正文;
当method=”get”时,enctype的默认值为null,get方式没有正文,所以就不需要enctype了。
1.3 多部件表单介绍
- 设置form表单的属性为enctype=”multipart/form-data”后,表单的请求正文部分则是由多个部件组成,每个部件对应一个表单字段,每个部件都有自己的头信息。头信息下面是空行,空行下面是字段的正文部分。多个部件之间使用随机生成的分隔线隔开。
- 多部件表单的特点:
1). 分隔出多个部件,即一个表单项一个部件。
2). 一个部件中自己包含请求头和空行,以及请求体。
3). 普通表单项:
请求头(1个):Content-Disposition:包含name=“xxxx”,即表单项名称。
请求体:表单项的值
4). 文件表单项:
请求头(2个):
Content-Disposition:包含name=“xxxx”,即表单项名称;还有一个filename=“xxx”,表示上传文件的名称
Content-Type:它是上传文件的MIME类型,例如:image/pjpeg,表示上传的是图片,图上中jpg扩展名的图片。
请求体:体就是上传文件的内容。
1.4 文件上传对Servlet的要求
- 不能再使用request.getParametere(“xxx”)获取表单数据;
这个方法在表单为enctype="multipart/form-data"时,无法获取请求参数,永远都返回null,因为request.getParameter(String)方法获取指定的表单字段字符内容,但文件上传表单已经不在是字符内容,而是字节内容,所以失效。 - 可以使用ServletInputStream request.getInputStream()获取所有的表单数据,它对应整个表单的正文部分。
1.5 commons-fileupload
使用ServletInputStream request.getInputStream()获取整个表单的请求正文信息后,我们需要解析流中的数据。当然解析它是很麻烦的一件事情,而Apache已经帮我们提供了解析它的工具:commons-fileupload。
-
fileupload概述:
fileupload是由apache的commons组件提供的上传组件。它最主要的工作就是帮我们解析request.getInputStream(),解析后的结果是一个表单项数据封装到一个FileItem对象中。我们只需要调用FileItem的方法即可! -
fileupload使用前准备(两个jar包):
commons-fileupload.jar:核心包;
commons-io.jar:依赖包。 -
fileupload的核心类有:
工厂类:DiskFileItemFactory
解析器类:ServletFileUpload
表单项类:FileItem -
fileupload组件的使用步骤:
1).创建工厂类对象:DiskFileItemFactory factory = new DiskFileItemFactory()
2).使用工厂创建解析器对象:ServletFileUpload fileUpload = new ServletFileUpload(factory)
3).使用解析器来解析request对象:List list = fileUpload.parseRequest(request) -
FileItem类介绍:
一个FileItem对象对应一个表单项(表单字段)。一个表单中存在文件字段和普通字段,可以使用FileItem类的isFormField()方法来判断表单字段是否为普通字段,如果不是普通字段,那么就是文件字段了。下面列出FileItem类的常用方法:
String getName():获取文件字段的文件名称;
String getString():获取字段的内容,如果是文件字段,那么获取的是文件内容,当然上传的文件必须是文本文件;
String getFieldName():获取字段名称,例如:,返回的是username;
String getContentType():获取上传的文件的类型,例如:text/plain。
int getSize():获取上传文件的大小;
boolean isFormField():判断当前表单字段是否为普通文本字段,如果返回false,说明是文件字段;
InputStream getInputStream():获取上传文件对应的输入流;
void write(File):把上传的文件保存到指定文件中。 -
简单上传示例
写一个简单的上传示例:
表单包含一个用户名字段,以及一个文件字段;
Servlet保存上传的文件到uploads目录,显示用户名,文件名,文件大小,文件类型。
第一步:
完成index.jsp,只需要一个表单。注意表单必须是post的,而且enctype必须是mulitpart/form-data的。
<form action="${pageContext.request.contextPath }/FileUploadServlet" method="post" enctype="multipart/form-data">
用户名:<input type="text" name="username"/><br/>
文件1:<input type="file" name="file1"/><br/>
<input type="submit" value="提交"/>
</form>
第二步:
完成FileUploadServlet
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 因为要使用response打印,所以设置其编码
response.setContentType("text/html;charset=utf-8");
// 创建工厂
DiskFileItemFactory dfif = new DiskFileItemFactory();
// 使用工厂创建解析器对象
ServletFileUpload fileUpload = new ServletFileUpload(dfif);
try {
// 使用解析器对象解析request,得到FileItem列表
List<FileItem> list = fileUpload.parseRequest(request);
// 遍历所有表单项
for(FileItem fileItem : list) {
// 如果当前表单项为普通表单项
if(fileItem.isFormField()) {
// 获取当前表单项的字段名称
String fieldName = fileItem.getFieldName();
// 如果当前表单项的字段名为username
if(fieldName.equals("username")) {
// 打印当前表单项的内容,即用户在username表单项中输入的内容
response.getWriter().print("用户名:" + fileItem.getString() + "<br/>");
}
} else {//如果当前表单项不是普通表单项,说明就是文件字段
String name = fileItem.getName();//获取上传文件的名称
// 如果上传的文件名称为空,即没有指定上传文件
if(name == null || name.isEmpty()) {
continue;
}
// 获取真实路径,对应${项目目录}/uploads,当然,这个目录必须存在
String savepath = this.getServletContext().getRealPath("/uploads");
// 通过uploads目录和文件名称来创建File对象
File file = new File(savepath, name);
// 把上传文件保存到指定位置
fileItem.write(file);
// 打印上传文件的名称
response.getWriter().print("上传文件名:" + name + "<br/>");
// 打印上传文件的大小
response.getWriter().print("上传文件大小:" + fileItem.getSize() + "<br/>");
// 打印上传文件的类型
response.getWriter().print("上传文件类型:" + fileItem.getContentType() + "<br/>");
}
}
} catch (Exception e) {
throw new ServletException(e);
}
}
1.6 文件上传的细节
- 将文件保存到WEB-INF下:
可以防止浏览器直接访问到资源文件。 - 文件名称相关问题:
有的浏览器上传的文件名是绝对路径,如:C:\files\baibing.jpg,这时需要切割。
String filename = fi2.getName();
int index = filename.lastIndexOf("\\");
if(index != -1) {
filename = filename.substring(index+1);
}
-
文件名乱码或者普通表单项乱码问题:
可通过设置:request.setCharacterEncoding(“utf-8”)或调用servletFileUpload.setHeaderEncoding(“utf-8”)解决上传文件名乱码问题。注:fileupload的内部会调用request.getCharacterEncoding();
-
文件同名问题:
解决方案:为每个文件添加名称前缀,这个前缀要保证不能重复。
例如:filename = CommonUtils.uuid() + “_” + filename; -
目录打散:
不能在一个目录下存放太多文件,太多文件会导致文件检索及操作变慢。一般有以下几种文件目录打散方式:
1).首字符打散:使用文件的首字母做为目录名称,例如:abc.txt,那么我们把文件保存到a目录下。如果a目录这时不存在,那么创建之。
2).时间打散:使用当前日期做为目录。
3).哈希打散:
a.通过文件名称得到int值,即调用hashCode()
b.它int值转换成16进制0~9, A~F
c.获取16进制的前两位用来生成目录,目录为二层!例如:1B2C3D4E5F,/1/B/保存文件。 -
上传文件的大小限制:
1).单个文件大小限制
sfu.setFileSizeMax(100*1024):限制单个文件大小为100KB
上面的方法调用,必须在解析开始之前调用!
如果上传的文件超出限制,在parseRequest()方法执行时,会抛出异常:FileUploadBase.FileSizeLimitExceededException2).整个请求所有数据大小限制
sfu.setSizeMax(1024 * 1024):限制整个表单大小为1M
这个方法也是必须在parseRequest()方法之前调用
如果上传的文件超出限制,在parseRequest()方法执行时,会抛出异常:FileUploadBase.SizeLimitExceededException -
缓存大小与临时目录:
1).缓存大小:指超过多大值,才向硬盘保存!默认为10KB
2).临时目录:向硬盘的什么目录保存
3).设置缓存大小与临时目录:new DiskFileItemFactory(20*1024, new File(“F:/temp”))
二、文件下载
1.1 文件下载介绍
下载就是向客户端响应字节数据,把一个文件变成字节数组,使用response.getOutputStream()来响应输出给浏览器。
1.2 文件下载的要求
两个头一个流!
- Content-Type:
表示传递给客户端的文件是什么MIME类型,例如:image/pjpeg
可以通过文件名称调用ServletContext的getMimeType()方法,得到MIME类型。 - Content-Disposition:它的默认值为inline,表示在浏览器窗口中打开,attachment;filename=“文件名称”
- 流:要下载的文件数据!
自己new一个输入流即可!
1.3 下载的细节
显示在下载框中的文件名称为中文时会出现乱码,处理中文乱码的通用方案:
filename = new String(filename.getBytes(“GBK”), “ISO-8859-1”);
1.4 下载示例
public class DownloadServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//本地文件路径
String fileUrl = "D:/考拉.jpg";
//所有浏览器都会使用本地编码,即中文操作系统使用GBK,浏览器收到这个文件名后,会使用iso-8859-1来解码
String filename = new String("考拉.jpg".getBytes("GBK"), "ISO-8859-1");
//通过文件名称获取文件的MIME类型
String contentType = this.getServletContext().getMimeType(fileUrl);
//获取文件资源的输入流
FileInputStream input = new FileInputStream(fileUrl);
//设置头
response.setHeader("Content-Type",contentType);
response.setHeader("Content-Disposition", "attachment;filename=" + filename);
//响应输出流
ServletOutputStream output = response.getOutputStream();
//利用common-io工具类的copy方法把输入流中的数据写入到输出流输出
IOUtils.copy(input, output);
input.close();
}
}