一文件上传
1文件上传对页面的要求
1.1必须是表单,不能是超链接;
1.2表单的提交方法必须是post,不能是get;
1.3表单的enctype必须是multipart/form-data类型;
1.4在表单中添加<input type="file" name="">
2对比文件上传表单和普通文本表单的区别
2.1对普通文本表单的测试
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>普通表单</title>
</head>
<body>
<form action="${pageContext.request.contextPath }/AServlet" method="post"">
<input type="text" name="username"><br/>
<input type="file" name="file1"><br/>
<input type="file" name="file2"><br/>
<input type="submit" value="提交">
</form>
</body>
</html>
浏览器输入内容:
2.2对文件上传表单的测试
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>普通表单</title>
</head>
<body>
<form action="${pageContext.request.contextPath }/AServlet" method="post"" enctype="multipart/form-data">
<input type="text" name="username"><br/>
<input type="file" name="file1"><br/>
<input type="file" name="file2"><br/>
<input type="submit" value="提交">
</form>
</body>
</html>
浏览器输入内容
查看提交后的内容
查看表单的请求数据正文部分,发现正文部分是由多个部件组成,每个部件对应一个表单字段,每个部件都有自己的头信息。头信息下面是空行,空行下面是字段的正文部分。多个部件之间使用随机生成的分隔线隔开。
文本字段的头信息中只包含一条头信息,即Content-Disposition,这个头信息的值有两个部分,第一部分是固定的,即form-data,第二部分为字段的名称。在空行后面就是正文部分了,正文部分就是在文本框中填写的内容。
文件字段的头信息中包含两条头信息,Content-Disposition和Content-Type。Content-Disposition中多出一个filename,它指定的是上传的文件名称。而Content-Type指定的是上传文件的类型。文件字段的正文部分就是文件的内容。
3文件上传对Servlet的要求
当提交的表单是文件上传表单时,这时,对Servlet也是有一定要求的。但我们必须明确的一点是,就算是文件上传表单,其上传数据也是被封装在了request中。
request.getParameter(String name)获得是的表单中指定字段的字符内容,但文件上传表单也不再是字符内容,而是字节内容,所以这个方法已经失效。这时可以使用request的getInputStream方法得到ServletInputStream对象,它是InputStream的子类对象,这个ServletInputStream对象对应整个表单的正文内容,真好是我们需要解析的流内容。当然解析它比较麻烦,所以通常情况下,我们可以一个工具类,Apache下的commons-fileupload;
4commoms-fileupload组件的简单介绍
4.1fileupload概述
fileupload组件是Apache下commons组件下的上传组件,主要帮我们解析request.getInputStream();
fileupload组件需要的jar包有:
commons-fileupload.jar,核心包;
commons-io.jar,依赖包。
4.2fileupload简单应用
fileupload的核心类有:DiskFileItemFactory、ServletFileUpload、FileItem。
使用fileupload组件的步骤如下:
1.创建工厂类DiskFileItemFactory对象:DiskFileItemFactory factory = new DiskFileItemFactory()
2.使用工厂创建解析器对象:ServletFileUpload fileUpload = new ServletFileUpload(factory)
3.使用解析器来解析request对象:List<FileItem> list = fileUpload.parseRequest(request)
主要介绍下FileItem对象,因为它才是我们最终要的结果。一个FileItem对象对应一个表单项(表单字段)。一个表单中存在文件字段和普通字段,可以使用FileItem类的isFormField()方法来判断表单字段是否为普通字段,如果不是普通字段,那么就是文件字段了。
还有一些的方法如下:
String getName()
方法介绍:获取文件字段的文件名称;
String getString()
方法介绍:获取字段的内容,如果是文件字段,那么获取的是文件内容,当然上传的内容必须是文本文件;
String getFieldName()
方法介绍:获取字段名字,注意和上面提到的getName方法进行区别;
String getContentType()
方法介绍:获取上传文件的类型;
int getSize();
方法介绍:获取上传文件的大小;
boolean isFormField()
方法介绍:判断当店表单字段是否为普通文本字段,如果返回false,说明是文件字段;
InputStream getInputStrean()
方法介绍:获取上传文件对应的输入流;
void write(File file)
方法介绍:把上传的文件保存到指定的文件中;
5文件上传的细节
51把上传的文件保存到web-inf目录下
如果没有把上传的文件保存到web-inf目录下,那么是相当危险的操作,因为如果有人恶意上传文件,比如说是一个jsp,然后运行这个jsp,鬼知道他在jsp文件里干了什么?想想是不是觉得很可怕???
5.2文件名称问题
有些浏览器,比如说是IE6,在得到文件名称的时候,可能是个带有前缀的绝对地址,这个时候我们就要自己通过截取字符串的方式来得到文件名;
5.3文件名中文乱码问题
当上传的文件的文件名包含中文的时候,就要自己设置编码了,commons-fileupload给我们提供了两种设置编码的方法:
request.setCharacterEncoding(String):这种方式是我们最为熟悉的方式了;
fileUpload.setHeaderEncdoing(String):这种方式的优先级高于前一种。
5.4上传文件同名问题
在实际过程中,我们绝对有可能遇到文件同名的问题,这个时候我们就可以重新给上传的问题进行命名,比如说UUID的方式;
5.5一个目录不能存放过多的文件
如果一个目录存放了过多的文件,就会出现打开速度变慢的问题,所以我们可以考虑在多个目录中存放上传的文件,这也称之为目录打散。
5.6上传的单个文件的大小限制
可以通过编码的方式来设置文件上传时当个文件的大小,ServletFileUpload类的setFileSizeMax(ling)就可以了。参数就是文件上传的上限字节数,例如:servletFileUpload.setFileSizeMax(1024*20)表示上限为10kb。一旦上传的文件超多了上限,那么就会抛出FileUploadBase.fileSizeLimitExceededException异常。我们就可以通过捕获这个异常,从而做下一步的操作。
5.7上传文件的总大小限制
有时我们需要限制一个请求的大小,也就是说个请求的最大字节数(所有表单项之和)。实现这一功能也非常简单,只需要调用ServletFileUpload类的setSizeMax(long)方法,例如:servletFileUpload.setSizeMax(1024*30),则说明真个表单的最大字节数是30kb,如果超出限制,Servlet类的parseRequest()方法会抛出FileUploadBase.SizeLimitExceededException异常。
5.8缓存大小与临时目录
大家想一想,如果我们在上传一个很大的文件时,比如说达到了几个G,先把电影保存到内存中,然后再通过内存copy到服务器的硬盘上,那你的内存能吃的消吗?所以fileupload不可能把文件都保存在内存中,fileupload会判断文件大小是否超出了10kb,如果是那么就把文件保存到硬盘上,如果没有超出,那么就保存在内存中。10KB是fileupload默认的值,我们可以来设置它。当文件保存到硬盘时,fileupload是把文件保存到系统临时目录,当然你也可以去设置临时目录。
代码示例:
jsp代码:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>上传</title>
</head>
<body>
${msg }
<form action="${pageContext.request.contextPath }/UploadServlet" method="post" enctype="multipart/form-data">
<input type="text" name="filaName"><br/>
<input type="file" name="file"><br/>
<input type="submit" value="提交">
</form>
</body>
</html>
Servlet代码:
package cn.ccnu.upload;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.UUID;
import javax.servlet.ServletException;
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.FileUpload;
import org.apache.commons.fileupload.FileUploadBase;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
public class UploadServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
System.out.println("文件上传!!!");
request.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
/*
* 文件上传三步曲:
* 1,工厂
* 2,解析器
* 3,表单项
*
*/
//获得文件上传的工厂,同时设置缓存大小为20kb,如果超出20kb就会保存到临时目录
// DiskFileItemFactory factory = new DiskFileItemFactory(1024*20, new File("F://temp"));
//不设置缓存大小的话,默认为10kb,临时目录为系统默认目录
DiskFileItemFactory factory = new DiskFileItemFactory();
//获得解析器
ServletFileUpload sfu = new ServletFileUpload(factory);
//设置单个文件的字节上限
// sfu.setFileSizeMax(1024 * 10);
//设置整个表单的文件字节上限
// sfu.setSizeMax(1024 * 50);
//注意上面的两句要在调用parseRequest()之前
try {
//获得所有的文件表单项
List<FileItem> fileItemList = sfu.parseRequest(request);
//对所获得的文件表单项进行循环
for (FileItem fileItem : fileItemList) {
//如果不是普通表单项,即是文件表单项
if(!fileItem.isFormField()){
//得到上传文件的文件名
String fileName = fileItem.getName();
//判断文件名是否为空,如果为空,则开始下一次循环
if(fileName == null || fileName.isEmpty()){
continue;
}
//因为有些浏览器在上传文件时,会得到文件的绝对路径,所以这个时候
//要通过截取得到文件的文件名
int index = fileName.indexOf("\\");
if(index != -1){
fileName = fileName.substring(index + 1);
}
//为了防止上传的文件出现文件名重复的问题,应该重新命名文件
fileName = UUID.randomUUID().toString().replace("-", "").toLowerCase() + "-" + fileName;
/*
* 文件的保存路径虽然没有规定一定保存在哪里,但最好是保存在web-inf目录下,
* 因为这个目录不能直接供外界访问
*/
//保存路径
String root = this.getServletContext().getRealPath("WEB-INF/files");
//打散目录
/*
* 打散目录,为什么要打散目录?
* 因为如果只使用一个目录的话,会随着所在目录文件越来越多,导致读取速度变慢
* 这里使用的打散目录的方法为:取文件名的hashCode,然后转化成16进制的字符
* 取前面两位字符,形成二级目录
*/
//得到文件名的hashCode
int hCode = fileName.hashCode();
//转换成16进制
String hex = Integer.toHexString(hCode);
//取hex的前两个字母,与root一起组成一个完整的目录
File savePath = new File(root, hex.charAt(0) + "/" + hex.charAt(1));
//创建目录链
savePath.mkdirs();
//创建目录文件
File file = new File(savePath, fileName);
//保存
fileItem.write(file);
}
}
} catch (Exception e) {
if(e instanceof FileUploadBase.FileSizeLimitExceededException){
request.setAttribute("msg", "上传文件超过10kb");
request.getRequestDispatcher("/form/upload.jsp").forward(request, response);
return;
}
throw new RuntimeException();
}
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
二文件下载
1什么是下载
下载就是向客户端响应字节数据!原来我们响应的都是html的字符数据!而文件下载,响应的是字节数据,即把一个文件变成字节数组,使用response.getOutputStream()来响应给浏览器!!!
2下载的要点
简单说就是:两个头一个流!
两个头
Content-Type:你传递给客户端的文件是什么MIME类型,例如:image/pjpeg;可以直接设置MIME类型,也可以通过文件名称调用ServletContext的getMimeType()方法,得到MIME类型!
Content-Disposition:它的默认值为inline,表示在浏览器窗口中打开!现在将其设置为attachment,表示不在浏览器中打开,即下载的方式。后面的filename表示显示在下载框中的文件名称。其形式为:attachment;filename=xxx
一个流
流:要下载的文件数据!
代码示例:
package cn.ccnu.download;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URLEncoder;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import sun.misc.BASE64Encoder;
public class DownloadServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
/*
* 两个头一个流
* 两个头:
* 1. Content-Type,传递给客服端的数据是什么MIME类型
* 2. Content-Disposition,默认是inline表示在浏览器中打开,如果是下载的话
* 应该被设置成attachment
* 流,就是数据输出流
*
*/
String fileName = "C:\\Users\\Jason\\Desktop\\常用资料\\按键.jpg";
//防止显示的中文乱码,其中的一种解决办法
String framename = new String("按键".getBytes("UTF-8"), "ISO-8859-1");
//获得文件的MIME类型,这里使用的是根据文件名称来获得
String contentType = this.getServletContext().getMimeType(fileName);
//设置以下载的方法打开文件
String contentDisposition = "attachment;filename=" + framename;
//设置两个头
response.setHeader("Content-Type", contentType);
response.setHeader("Content-Disposition", contentDisposition);
//得到一个输入流
FileInputStream fin = new FileInputStream(fileName);
//获取绑定了响应段的输出流
ServletOutputStream out = response.getOutputStream();
//创建缓冲区
byte[] buffer = new byte[1024];
int length = 0;
//循环将输入流中的数据读到缓冲区中
while((length = fin.read(buffer)) > 0){
out.write(buffer, 0, length);
}
//关闭两个流
fin.close();
out.close();
}
// 用来对下载的文件名称进行编码的!
//因为浏览器的不同,有些浏览器采用base64编码,有些采用URL编码
//这是一种比较通用的解决办法
public static String filenameEncoding(String filename, HttpServletRequest request) throws IOException {
String agent = request.getHeader("User-Agent"); //获取浏览器
if (agent.contains("Firefox")) {
BASE64Encoder base64Encoder = new BASE64Encoder();
filename = "=?utf-8?B?"
+ base64Encoder.encode(filename.getBytes("utf-8"))
+ "?=";
} else if(agent.contains("MSIE")) {
filename = URLEncoder.encode(filename, "utf-8");
} else {
filename = URLEncoder.encode(filename, "utf-8");
}
return filename;
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
3下载的细节
在下载的过程中需要注意的细节就是显示的文件名的中文乱码问题,因为不同的浏览器采用不同的编码方式,比如说FireFox采用Base64编码,而对于其它大部分浏览都采用的是URL编码的方式。
比较通用的解决办法
1:String newFileName = new Stirng(oldName.getBytes("UTF-8"), "IOS-8859-1");
2:
public static String filenameEncoding(String filename, HttpServletRequest request) throws IOException {
String agent = request.getHeader("User-Agent"); //获取浏览器
if (agent.contains("Firefox")) {
BASE64Encoder base64Encoder = new BASE64Encoder();
filename = "=?utf-8?B?"
+ base64Encoder.encode(filename.getBytes("utf-8"))
+ "?=";
} else if(agent.contains("MSIE")) {
filename = URLEncoder.encode(filename, "utf-8");
} else {
filename = URLEncoder.encode(filename, "utf-8");
}
return filename;
}