文件上传与下载
程序员文章站
2022-06-18 17:42:13
...
文件上传
- 文件上传:就是将客户端资源,通过网络传递到服务器端
- 原因:因为数据比较大,必须通过文件上传才可以完成将数据保存到服务器端操作。
- 文件上传的本质:IO流的操作
<form action = "${pageContext.request.contextPath}/upload1" method="post">
<input type="file" name="f"><br>
<input type="submit" value="上传">
</form>
文件上传注意事项
- 浏览器端:
- method=post 只有post才可以携带大数据
- 必须使用
<input type='file' name='f'>
要有name属性- 默认值encType=”application/x-www-form-urlencoded”
- 设置值:encType=”multipart/form-data”
- 服务器端:
- request对象是用于获取请求信息的。它有一个方法 getInputStream();可以获取一个字节输入流,通过这个流可以读取到所有的请求正文信息。
- 文件上传原理:
- 浏览器注意上述三件事,在服务器端通过流将数据读取到,再对数据进行解析。将上传文件内容得到,保存在服务器端,就完成了文件上传。
- 在实际开发中,不需要我们进行数据解析,完成文件上传。文件上传的工具(插件),已经封装好了,提供API,只要调用他们的API就可以完成文件上传。
- commons-fileupload,它是Apache提供的一套开源免费的文件上传工具。
如何在Servlet中读取文件上传数据,并保存到本地硬盘中
- Request 对象提供了一个getInputStream方法,通过这个方法可以读取到客户端提交过来的数据。但由于用户可能会同时上传多个文件,在servlet端编程直接读取上传数据,并分别解析出相应的文件的数据是一项非常麻烦的工作。
- 为方便用户处理文件上传数据,Apache开源组织提供了一个用来处理表单文件上传的一个开源组件(Commons-fileupload),该组件性能优异,并且其API使用及其简单,可以让开发人员轻松实现web文件上传功能,因此在web开发中实现文件上传功能,通常使用Commons-fileupload组件实现。
- 使用Commons-fileupload组件实现文件上传,需要导入该组件相应的支持jar包:Commons-fileupload和commons-io。commons-io不属于文件上传组件的开发jar文件,但Commons-fileupload组件从1.1版本开始,它工作时需要commons-io包的支持。
- 使用commons-fileupload
- 导入jar包
- commons-fileupload-1.2.1.jar 文件上传
- commons-io-1.4.jar 它是提供的IO工具
- commons-fileupload的三个核心
- DiskFileItemFactory类
- ServletFileUpload类
- FileItem类
快速入门
- 创建upload2.jsp页面
<form action= "${pageContext.request.contextPath}/upload2" method = "post" encType="multipart/form-data">
<input type="file" name="f"><br>
<input type="submit" value="上传">
</form>- 创建Upload2Servlet
- 创建一个DiskFileItemFactory
- DiskFileItemFactory factory = new DiskFileItemFactory();
- 创建ServletFileUpload类
- ServletFileUpload upload = new ServletFileUpload(factory);
- 解析所有上传数据
List<FileItem> items=upload.parseRequest(request);
- 遍历items集合,集合中的每一项,就是一个上传数据。
- isFormField();
- getFileName();
- 返回值String,得到组件名称
<input name="">
- getName();
- 返回值是String,得到的是上传文件的名称
- 注意:浏览器不同,它们得到的效果不一样
- 包含全路径名称 例如:c:\User\Administrator\Desktop\a.txt
- 只包含上传文件名称 例如:a.txt
- getString();
- 这个方法可以 获非上传组件的内容,相当于 getParameter方法作用
- 如果是上组件,上传的文件是文本文件,可以获取到文本文件的内容,如果不是文本文件,例如一张图片,这样获取并不合适。
- 获取上传文件的内容,保存到服务器端
*
- item.getInputStream();它是用于读取上传文件内容的输入流。
- 使用文件复制操作就可以完成文件上传
public class Upload2Servlet extends HttpServlet{
public void doGet(HttpServletRequest request,HttpServletResponse response) throws ServletException,IOException{
//创建DiskFileItemFactory
DiskFileItemFactory factory = new DiskFileItemFactory();
//创建ServletFileUpload类
ServletFileUpload upload = new ServletFileUpload(factory);
try{
//底层通过request获取数据,进行解析,将解析的数据封装到List<FileItem>
List<FileItem> items= upload.parseRequest(request);
//遍历集合
for(FileItem item:items){
if(item.isFormField()){
//这就是得到了<input type = "text" name = "content">这样的组件
String filedName=item.getFiledName();
System.out.println(filedName);
String name = item.getName();
}else{
//这就是得到了<input type = "file">这样的组件
String fieldName = item.getFieldName();
System.out.println("上传组件的名称:"+fileName);
String name = item.getName();//上传文件名称
name = name.subString(name.lastIndexOf("\\")+1);
//获取上传文件内容
InputString is = item.getInputStream();
byte[] b = new byte[1024];
int len = -1;
FileOutputStream fos = new FileOutputStream("d:/upload/"+name);
while((len=is.read(b)) != -1){
fos.write(b,0,len);
}
fos.close();
is.close();
}
}
}catch(FileUploadException e){
e.printStackTrace();
}
}
}
//获取上传文件内容,完成文件上传操作
FileOutputStream fos = new FileOutputStream("d:/upload/"+name);
IOUtils.copy(item.getInputStream(),output);
核心API介绍
- DiskFileItemFactory
- 作用:可以设置缓存大小以及临时文件保存位置
- 默认缓存大小是 10240(10k)
- 临时文件默认存储在系统的临时文件目录下(可以在环境变量中查看)
- new DiskFileItemFactory();
- 缓存大小与临时文件存储位置使用默认的
- DiskFileItemFactory(int sizeThreshold,File repository)
- sizeThreshold :缓存大小
- repository:临时文件存储位置
- 注意:对于无参数构造,也可以设置缓存大小以及临时文件存储位置
- setSizeThreshold(int sizeThreshold)
- setRepository(File repository)
public class Upload3Servlet extends HttpServlet{
public void doGet(HttpServletRequest request,HttpServletResponse response) throws ServletException,IOException{
//创建DiskFileItemFactory
//DiskFileItemFactory factory = new DiskFileItemFactory();//使用默认
//在webroot下创建一个temp文件作为临时文件存储位置
File file = new File(this.getServletContext().getRealPath("/temp"));//获取temp目录部署到Tomcat中的绝对磁盘路径
DiskFileItemFactiry factory = new DiskFileItemFactory(1024*100,file);//使用默认的
//创建ServletFileUpload
ServletFileUpload upload = new ServletFileUpload(factory);-
}
}
- ServletFileUpload
- ServletFileUpload upload = new ServletFileUpload(factory);
- 创建一个上传工具,指定使用缓冲区与临时文件存储位置
List<FileItem> items = upload.parseRequest(request);
- 它是用于解析request对象,得到所有上传项,每一个FileItem就相当于一个上传项。
- boolean flag = upload.isMultipartContent(request);
- 可以用于判断是否是上传。就是判断encType=”multipart/form-data”
- 设置上传文件大小
- void setFileSizeMax(long fileSizeMax)设置单个文件上传大小
- void setSizeMax(long sizeMax)设置总文件上传大小
- 解决上传文件中中文名称乱码
- setHeaderEncoding(“utf-8”);
- 注意:如果使用request.setCharacterEncoding(“utf-8”)也可以(全局编码过滤),但是不建议使用。
- void setHeaderEncoding(java.lang.String encoding)设置编码集,解决上传文件名乱码。
//创建ServletFileUpload
ServletFileUpload upload = new SErvletFileUpload(factory);
boolean flag = upload.isMultipartContent(request);//用于判断是否是长传操作
if(flag){
//解决上传文件名称中文乱码
upload.setHeaderEncoding("utf-8");
try{
List<FileItem> items = upload.parseRequest(request);//解决request,得到所有的上传项FileItem
//得到所有上传项
for(FileItem item:items){
if(item.isFormField()){
//非上传组件
System.out.println("组件名称:"+item.getFieldName());
System.out.pritnln("内容:"+item.getString());
}else{
//上传组件
System.out.println("组件名称:"+item.getFieldName());
System.out.println("上传文件名称:"+item.getName());
}
}
}catch(FileUploadException e){
e.printStackTrace();
}
}else{
response.getWriter().write("不是上传操作");
}
- FileItem
- isFormField
- 用于判断是否是上传组件
- 如果是
<input type="file">
返回的就是false,否则返回true- getFiledName()
- 返回值String,得打组件名称
<input name="">
- getName()
- 返回值是String,得到的是上传文件的名称
- 注意:浏览器不同,它们得到的效果不一样
- 包含全路径名称,例如:C:\Users\Administrator\Desktop\a.txt
- 只包含上传文件名称 ,例如:a.txt
- getString()
- 这个方法可以获取非上传组件的内容,相当于getParameter方法作用
- 问题:如果信息是中文,会出现乱码,解决方案 getString(“utf-8”);
- 如果是上传组件,上传的文件是文本文件,可以获取到文件的内容
- 如果不是文本文件,例如:是一张图片,这样获取合适吗?
- 获取上传文件的内容,保存到服务器端
- item.getInputStream();它是用于读取上传文件内容的输入流。
- 使用文件复制操作就可以完成文件上传。
- 删除临时文件
- item.delete();
关于文件上传时的乱码问题
- 上传文件名称乱码
- ServletFileUpload.setHeaderEncoding(“utf-8”);
- 非上传组件内容乱码
- FileItem.getString(“utf-8”);
- 上传文件信息是否会乱码,是否需要解决?
- 不需要解决,因为在上传时,使用的字节流来进行复制。
多文件上传
- 多文件上传的javascript编码
- 每次动态增加一个文件上传输入框,都把它和删除按钮放置在一个单独的div中,并对删除按钮的onclick事件进行相应,使之删除按钮所在的div
- 如:this.parentNode.parentNode.removeChild(this.parentNode);
- 文件上传的注意事项
- 上传文件在服务器端保存位置问题
- 保存在可以被浏览器直接访问的位置
- 例如:商城的商品图片
- 保存在工程的webRoot下的路径(不包含MeTa-INF 和web-INF目录及其子目录)
- 保存在不能被浏览器直接访问的位置
- 例如:付费的视频
- 保存·工程中的META-INF WEB-INF目录及其子目录
- 不在工程中的服务器的磁盘目录下
- 上传文件在同一个目录重名问题
- 如果文件重名,后上传的文件就会覆盖先上传的文件在开发中解决这个问题,可以给上传文件起随机名称
- 使用毫秒值
- 使用UUID,filename=UUID.randomUUID.toString+”_”+filename;
- 同一个目录下文件过多,只需要分目录就可以
- 按照上传时间进行目录分离(周,月)
- 按照上传用户进行目录分离——为每个用户建立单独目录
- 按照固定数量进行目录分离——-假设每个目录只能存放3000个文件,每当一个目录存满3000个文件后,创建一个新的目录。
- 按照文件名的hashCode进行目录分离
*
<head>
<script type="text/javascript">
functionaddFile(){
var div = document.getElementById("content");
div.innerHTML+="<div><input type='file' name='f'><input type='button' value='remove file'></div>";
}
function removeFile(btn){
document.getElementById("content").removeChild(btn.parentNode);
}
</script>
</head>
<body>
<input type="button" value="add file" onclick="addFile();"><br>
<form action="${pageContext.request.contextPath}/upload3" method="post" encType="multipart/form-data">
<input type="file" name="f"><br>
<div id = "content">
<div><input type='file' name='f'><input type='button' value='remove file' onclick='removeFile(this)'></div>
</div>
<input type="submit" value="上传">
</form>
</body>
//目录分离算法
public class FileUploadUtils{
public static String getRandomDirectory(String filename){
int hashcode = filename.hashCode();
//System.out.println(hashcode);
//int类型数据在内存中占32位,转换成16进制数,就得到8个16进制数
String hex = Integer.toHexString(hashcode);
//System.out.println(hex);//056d9363
//得到两层目录
return "/"+hex.charAt(0)+"/"+hex.charAt(1);
}
//方式2
public static String getRandomDirectory2(String filename){
//获取唯一文件名的hashCode
int hashcode = filename.hashCode();
int a = hashcode & 0xf;
//获得一级目录
hashcode = hashcode >>> 4;
//获得二级目录
int b = hashcode & 0xf;
return "/"+a+"/"+b;
}
public static void main(String[] args){
//生成两层目录
String path = getRandomDirectory("a.txt");//91067135
File file = new File("d:/upload");
File directory = new File(file,path);
//如果目录不存在,创建目录
if(!directory.exists()){
directory.mkdirs();
}
}
}
public class FileUploadUtils{
//得到上传文件真实名称 c:\a.txt
public static String getRealName(String filename){
int index = filename.lastIndexOf("\\")+1;
return filename.substring(index);
}
//获取随机名称
public static String getUUIDFileName(String filename){
int index = filename.lastIndexOf(".");
if(index != -1){
return UUID.randomUUID()+filename.substring(index);
}else{
return UUID.randomUUID().toString();
}
}
public static String getRandomDirectory(String filename){
}
}
文件下载
- 文件下载的方式
- 超链接下载
- 需要下载的资源存放在工程的webroot目录下
- 服务器端通过流下载
- 超链接下载
- download1.jsp
<a href='${pageContext.request.contextPath}/upload/a.bmp'>a.bmp</a><br></li>
<li><a href ='${pageContext.request.contextPath}/upload/a.doc'>a.doc</a><br></li>
<li><a href = '${pageContext.request.contextPath}/upload/a.txt'>a.txt</a><br></li>
<li><a href = '${pageContext.request.context}'>/upload/tk.mp3</a><br>- 注意:如果文件可以直接被浏览器解析,那么会在浏览器中直接打开,不能被浏览器直接解析,就是下载操作。直接打开的要想下载,右键另存为。
- 超链接下载,要求下载的资源,必须是可以直接被浏览器直接访问的(存放在工程的webroot下,如果放在磁盘目录D盘等,不能被直接访问)。
- 客户端访问服务器静态资源文件时,静态资源文件是通过缺省Servlet返回的。
- 在tomcat配置文件conf/web.xml找到—org.apache.catalina.servlets.DefaultServlet
- 在服务器端编程完成下载
- 创建download2.jsp,下载保存在D盘的资源
<a href='${pageContext.request.contextPath}/download?filename=a.bmp'>a.bmp</a><br></li>
<li><a href ='${pageContext.request.contextPath}/download?filename=a.doc'>a.doc</a><br></li>
<li><a href = '${pageContext.request.contextPath}/download?filename=a.txt'>a.txt</a><br></li>
<li><a href = '${pageContext.request.context}'>/download?filename=tk.mp3</a><br>- 创建DownloadServlet
- 得到要下载的文件名称
- String filename = request.getParameter(“filename”);
- 判断文件是否存在
- File file = new File(“d:/upload/” +filename);
- if(file.exists())
- 进行下载
- 原理:就是通过response获取一个输出流,将要下载的文件内容写回到浏览器端就可以了。
public class DownloadServlet extends HttpServlet{
public void doGet(HttpServletRequest request,HttpServletResponse response) throws ServletException,IOException{
//得到要下载的文件名称
String filename = request.getParameter("filename");
//解决资源名称中文乱码问题
filename = new String(filename.getBytes("iso8859-1"),"utf-8");
//在d:/upload目录下查找这个文件是否存在
File file = new File("d:/upload/"+filename);
if(file.exists()){
//文件存在,完成下载
//下载注意事项1--设置下载文件的mimeType
String mimeType = this.getServletContext().getMimeType(filename);
response.setContentType(mimeType);
//判断浏览器,把filename转换成指定的编码
String agent = request.getHeader("user-agent");
if(agent.contains("MSIE")){
//IE浏览器
filename = URLEncoder.encode(filename,"utf-8");
filename = filename.replace("+"," ");
}else if(agent.contains("Firefox")){
//火狐浏览器
BASE64Encoder base64Encoder = new BASE64Encoder();
filename = "=?utf-8?B?"+base64Encoder.encode(filename.getBytes("utf-8"))+"?=";
}else if(agent.contains("Chrome")){
//谷歌浏览器
filename = URLEncoder.encode(filename,"utf-8");
}else{
//其他浏览器
filename = URLEncoder.encode(filename,"utf-8");
}
//下载注意事项2---永远是下载
response.setHeader("content-disposition","attachment;filename="+filename);
FileInputStream fis = new FileInputStream(file);//读取要下载文件的内容
OutputStream os = response.getOutputStream();//将要下载的文件内容通过输出流写回到浏览器端
int len = -1;
byte[] b = new byte[1024*100];
while((len = fis.read(b)) != -1){
os.write(b,0,len);
os.flush();
}
os.close();
fis.close();
}else{
throw new RuntimeException("下载资源不存在。");
}
}
}
- 注意:要想通过编程的方式,实现文件下载
- 在servlet文件中,设置response.setContextType(“text/html;charset=utf-8”), .doc 和 .bmp等格式的资源,在浏览器端就会全部被作为文本格式打开。如果设置成response.setContextType(“image/bmp”),在浏览器端会被全部当成图片打开,如果设置成错误的格式,response.setContextType(“hudjor/ji34”) ;浏览器不能正常解析,就会弹出自动下载的窗口。编码方式种类的设置可以参考tomcat目录下web.xml的配置
- 要设置mimetype类型
- response.setContextType(String mimeType);
- 问题:怎样才能得到要下载文件的mimeType类型?
- ServletContext.getMimeType(String filename);
- 如果设置了mimeType,浏览器能解析的就直接展示了,不能解析的,直接下载。
- 设置一个响应头,设置后的效果,就是无论返回的是否可以被浏览器解析,就是下载。
- response.setHeader(“content-disposition”,”attachment;filename=下载文件名称”) ;
- 总结:服务器端编程下载
- 将下载的文件通过response.getOutputStream()。流写回到浏览器端
- 设置mimeType response.setContentType(getServletContext.getMimeType(String filename));
- 设置响应头,目的是下载资源的操作
- response.setHeader(“content-disposition”,”attachment;filename=下载文件名称”);
文件下载时的乱码问题
- 关于下载时中文名称资源查找不到
- 原因:
<a href ='${pageContext.request.contextPath}/download?filename=天空.mp3'>天空.mp3</a>
这是get请求- 在服务器端:
- String filename = request.getParameter(“filename”);
- 解决:new String(filename.getBytes(“iso8859-1”),”utf-8”);
- 下载文件显示时的中文乱码问题
- 原因:
response.setHeader("content-disposition","attachment;filename="+filename);
- 此处的filename就是弹出的下载框,所展示的名称
- IE:要求filename必须是utf-8码
- firefox:要求filename必须是base64编码
- 问题:如何判断浏览器?
- String useragent = request.getHeader(“user-agent”);
处理IE浏览器与Firefox浏览器乱码问题
String agent = request.getHeader("user-agent");
if(agent.contains("MSIE")){
//IE浏览器
filename = URLEncoder.encode(filename,"utf-8");
filename = filename.replace("+"," ");
}else if(agent.contains("Firefox")){
//火狐浏览器
BASE64Encoder base64Encoder = new BASE64Encoder();
filename = "=?utf-8?B?"+base64Encoder.encode(filename.getBytes("utf-8"))+"?=";
}else if(agent.contains("Chrome")){
//谷歌浏览器
filename = URLEncoder.encode(filename,"utf-8");
}else{
//其他浏览器
filename = URLEncoder.encode(filename,"utf-8");
}
使用队列来优化递归操作完成文件下载
<body>
<%!
//声明一个方法
public void getFile(File file){
if(file.isDirectory()){
//是目录
File[] fs = file.listFiles();
for(int i =0;i<fs.length;i++){
getFile(fs[i]);//递归调用
}
}else if(file.isFile()){
//是文件
System.out.println(file.getName());
}
}
%>
<%
String path="D:\\java1110\\workspace\\day22\\webRoot\\upload"
File uploadDirectory = new File(path);
getFile(uploadDirectory);
%>
</body>
对列特点:先进先出
- java.util.Queue
public interface Queue<E> extends Collection<E>
- 在jdk中有一个接口Queue,它有一个实现类叫LinkedList,它其实就是一个队列。
- 如果要使用队列,插入 offer 获取使用 poll
- 使用队列来优化递归操作:是可以解决目录层次过多问题。
- 原因:递归操作可以理解成纵向的遍历,如果目录层次比较多,在内存中存储的数据也多,会引起溢出。使用队列,它是横向遍历,一层一层遍历,可以解决目录层次比较多问题。
- 因为使用队列,最多时候在内存中只存储了一层的信息。
<!--使用队列完成下载upload目录下所有文件-->
<body>
<%
String path = "D:\\java1110\\workspace\\day22\\webRoot\\upload";
File uploadDirectory = new File(path);
//创建一个队列
Queue<File> queue = new LinkedList<File>();
queue.offer(uploadDirectory);
while(!queue.isEmpty()){//如果队列不为空
File f = queue.poll();//从队列中获取一个File
if(f.isDirectory()){//是目录,将目录下所有文件遍历出来,存储到队列中
File[] fs = f.listFiles();
for(int i=0;i<fs.length;i++){
queue.offer(fs[i]);
}
}else{
String absolutePath=(f.getAbsolutePath());
String p = absolutePath.substring(absolutePath.lastIndexOf("\\upload"));
out.println("<a href='/day22"+p+"'>"+f.getName()+"</a><br>");
}
}
%>
</body>