【Java EE 学习 22 上】【文件上传】【目录打散】【文件重命名】

2023-04-24,,

1.文件上传概述

  (1)使用<input type="file">的方式来声明一个文件域。

  (2)表单提交方式一定要是post方式才行

  (3)表单属性enctype

    默认提交属性:application/x-www-form-urlencoded    这个类型表示传递的是键值对类型

      需要改成:multipart/form-data             这个类型表示将要传递的是字节码类型

  (4)使用httpWatch查看http协议的请求部分,可以发现

    

    在Content-type后面的boundary是分界符,用以分割头部信息和正文部分,正文部分是以开始的部分,结束部分则以来标识,可以看出结束的标识比开始的标志多了两个--。

2.文件上传准备

  (1)需要的两个jar包:

    commons-fileupload.jar  :文件上传的核心包

    commons-io.jar      :文件上传可能会使用的工具包,该工具包中提供了一个从流中将数据写到文件的简便方法。

  (2)几个核心类

    DiskFileItemFactory类

      |-方法setSizeThreshold  :设置一个临界值,如果上传的单个文件小于该值,将不会借助临时临时文件存储上传的文件;反之,如果大于该值则服务器将会借助临时文件暂存上传的文件,默认值是10KB。

      |-方法setRepository     :设置一个临时目录用于暂存上传的数据。

    ServletFileUpload类

      |-该类接收DiskFileItemFactory类的实例

      |-setSizeMax方法     :设置可接受的上传的所有文件的大小之和

      |-setFileSizeMax方法    :设置可以上传的单个文件的大小

      |-parseRequest方法    :解析HttpServletRequest对象,从中拿到所有上传文件的信息,返回List结果集

    FileItem类

      |-该类是ServletFileUpload类的方法parseRequest的返回值List的元素类型即List<FileItem>

      |-getName方法      :获取上传文件的文件名,带有全路径

      |-getContentType     :得到文件类型,该类型并不是判断后缀名得到的,也就是说即使更改文本文件的扩展名为.jpg,文件类型也不会是图片,仍然是文本类型

      |-getInputStream     :该方法返回一个输入字节流,通过读取该字节流就可以获得该文件并保存到服务器。

      |-delete方法        :该方法的作用是销毁FileItem对象,在销毁该对象的同时也会在服务器上将对应的临时文件删除,虽然理论上就算不调用该方法,临时文件也会最终被删除,但是强烈推荐手动调用该方法,这样可以尽快删除临时文件,节省存储空间,但是应当重点强调的是,调用该方法的时候必须将输入流和输出流统统close掉,否则可能会由于文件占用而导致临时文件删除不掉。

3.上传单个文件

  (1)JSP文件

<%@ page language="java" import="java.util.*" pageEncoding="utf-8"
contentType="text/html; charset=utf-8" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!-- 简单的文件上传示例 -->
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
<title>Insert title here!</title>
<style type="text/css">
table{
border: 1px solid black;
border-collapse: collapse;
margin: 0 auto;
margin-top: 100px;
}
td {
border: 1px solid black;
text-align: center;
padding: 3px;
}
</style>
</head> <body>
<form action="<c:url value='/uploadSingleFileServlet1'/>" method="post" enctype="multipart/form-data">
<table>
<tr>
<td>文件</td>
<td><input type="file" name="file"></td>
</tr>
<tr>
<td colspan="2"><input type="submit" value="上传"></td>
</tr>
</table>
</form>
</body>
</html>

uploadSingleFile.jsp

  (2)Servlet文件

package com.kdyzm.servlet;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.List; 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.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload; public class UploadSingleFileServlet1 extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
this.doPost(request, response);
} public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=utf-8");
//接收上传的请求,读取上传的文件
//获取存放上传文件的上传文件夹
String path=this.getServletContext().getRealPath("/upload");
//第一步:声明一个磁盘文件项目工厂类,用于指定一个磁盘空间存放上传文件
DiskFileItemFactory dfif=new DiskFileItemFactory();
dfif.setRepository(new File(this.getServletContext().getRealPath("/temp")));//声明缓存目录
dfif.setSizeThreshold(1024*10);//小于10KB留在内存,大于10KB保存到文件
//第二步:声明ServletFileUpload接收上面的目录
ServletFileUpload sfu=new ServletFileUpload(dfif);
//第三步:解析Request
try {
List<FileItem>list=sfu.parseRequest(request);//解析请求HttpServletRequest
//假设只有一个文件
FileItem file=list.get(0);
//获取文件名,改文件名带有路径
String fileName=file.getName();
fileName=fileName.substring(fileName.lastIndexOf("\\")+1);
//获取文件类型
String fileType=file.getContentType();
//获取输入字节流
InputStream is=file.getInputStream();
long fileSize=is.available();
FileOutputStream fos =new FileOutputStream(new File(path+"/"+fileName));
int length=-1;
byte []buff=new byte[1024*1024];
while((length=is.read(buff))!=-1)
{
fos.write(buff, 0, length);
}
fos.close();
is.close();//这个流必须关闭,否则不能删除临时文件
//删除临时文件的动作必须在流关闭之后。应当是文件占用的问题造成的失败。
file.delete();
PrintWriter pw=response.getWriter();
pw.println("上传成功!<br/>文件名为:"+fileName);
pw.println("<br/>文件大小为:"+fileSize+"Byte");
pw.println("<br/>文件类型为:"+fileType);
//删除临时文件,伴随着FileItem对象的删除,实际存储在磁盘上的文件也被删除
} catch (FileUploadException e) {
e.printStackTrace();
}
}
}

UploadSingleFileServlet.java

  (3)效果演示

       开始上传

      

        上传成功之后浏览器提示

      

      服务器查看上传的文件

      

4.强烈注意:一定要在tomcat中的相应目录下查看上传的文件,而不能在myeclipse中的项目文件夹下查看,但是myeclipse中的项目文件夹必须存在才行!!

5.上传多个文件

  (1)JSP文件

<%@ page language="java" import="java.util.*" pageEncoding="utf-8"
contentType="text/html; charset=utf-8" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!-- 上传多个文件示例 -->
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
<title>Insert title here!</title>
<style type="text/css">
table{
border: 1px solid black;
border-collapse: collapse;
margin: 0 auto;
margin-top: 100px;
}
td {
border: 1px solid black;
text-align: center;
padding: 3px;
}
</style>
</head> <body>
<form action="<c:url value='/uploadMultipleFiles'/>" method="post" enctype="multipart/form-data">
<table>
<tr>
<td>文件</td>
<td><input type="file" name="file1"></td>
</tr>
<tr>
<td>文件</td>
<td><input type="file" name="file2"></td>
</tr>
<tr>
<td>文件</td>
<td><input type="file" name="file3"></td>
</tr>
<tr>
<td colspan="2"><input type="submit" value="上传"></td>
</tr>
</table>
</form>
</body>
</html>

uploadMultipleFiles.jsp

  (2)Servlet文件

package com.kdyzm.servlet;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.Iterator;
import java.util.List; 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.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
/*
* 处理多个文件同时上传的情况的Servlet
* @author kdyzm
*
*/
public class UploadMultipleFilesServlet1 extends HttpServlet {
private static final long serialVersionUID = 1L; public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
this.doPost(request, response);
} public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=utf-8"); //首先获得存放上传文件目录的目录路径以及存放临时文件的目录
String savaPath=this.getServletContext().getRealPath("/upload");
String tempDir=this.getServletContext().getRealPath("/temp"); //第一步:获取磁盘文件项目工厂并设置相关参数
DiskFileItemFactory dfif=new DiskFileItemFactory();
dfif.setRepository(new File(tempDir));
dfif.setSizeThreshold(1024*10); //第二步:声明ServletFileUpload接收DiskFileItemFactory对象
ServletFileUpload sfu=new ServletFileUpload(dfif); //第三步:开始接收请求并将文件保存到upload目录
try {
List<FileItem>list=sfu.parseRequest(request);
//遍历List
Iterator<FileItem>it=list.iterator();
PrintWriter pw=response.getWriter();
while(it.hasNext())
{
FileItem file=it.next();
String fileName=file.getName();
fileName=fileName.substring(fileName.lastIndexOf("\\")+1);
String fileType=file.getContentType();
InputStream is=file.getInputStream();
long fileSize=is.available();
FileOutputStream fos=new FileOutputStream(new File(savaPath+"/"+fileName));
int length=-1;
byte []buff=new byte[1024*1024];
while((length=is.read(buff))!=-1)
{
fos.write(buff,0,length);
}
fos.close();
is.close();
file.delete();
pw.println("上传成功!");
pw.println("<br/>文件名:"+fileName);
pw.println("<br/>文件类型:"+fileType);
pw.println("<br/>文件大小:"+fileSize+"Byte<hr/>");
}
} catch (FileUploadException e) {
e.printStackTrace();
}
} }

UploadMultipleFilesServlet

  (3)效果演示

     开始上传:

  

    上传成功:

    

    服务器查看文件:

    

6.目录打散

  (1)使用该技术的原因:如果使用一个目录保存几千张、几万张图片,那么该目录的打开速度、读写速度都会变得非常慢,但是如果对该目录进行了处理,划分了二级目录甚至三级目录的话,该目录的读写性能和打开速度基本上会保持不变,因为将所有图片分散到了所有的子目录中,单个目录中的图片数量就变得合理了。

  (2)文件名到目录的映射关系:

     第一级目录:使用文件名的hash值的低半个字节即  hashCode&0XF

     第二级目录:使用文件名的hash值的高半个字节即  (hashCode>>4)&0XF

  (3)jsp文件

<%@ page language="java" import="java.util.*" pageEncoding="utf-8"
contentType="text/html; charset=utf-8" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!-- 目录打散问题的解决 -->
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
<title>Insert title here!</title>
<style type="text/css">
table{
border: 1px solid black;
border-collapse: collapse;
margin: 0 auto;
margin-top: 100px;
}
td {
border: 1px solid black;
text-align: center;
padding: 3px;
}
</style>
</head> <body>
<form action="<c:url value='/catalogBreakUp'/>" method="post" enctype="multipart/form-data">
<table>
<tr>
<td>文件</td>
<td><input type="file" name="file1"></td>
</tr>
<tr>
<td>文件</td>
<td><input type="file" name="file2"></td>
</tr>
<tr>
<td>文件</td>
<td><input type="file" name="file3"></td>
</tr>
<tr>
<td colspan="2"><input type="submit" value="上传"></td>
</tr>
</table>
</form>
</body>
</html>

catalogBreakUp.jsp

  (4)Servlet文件

package com.kdyzm.servlet;
/*
* 使用目录打散实现文件重命名和提升性能。
*/
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.Iterator;
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.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload; public class CatalogBreakUp extends HttpServlet {
private static final long serialVersionUID = 1L; public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
this.doPost(request, response);
} public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=utf-8");
PrintWriter pw=response.getWriter(); //1.获得保存上传文件的目录的路径以及缓存文件的目录
String savaPath=this.getServletContext().getRealPath("upload");
String tempPath=this.getServletContext().getRealPath("temp"); //2.设置磁盘文件项目工厂类的实例并设置缓存目录以及文件大小设置
DiskFileItemFactory dfif=new DiskFileItemFactory();
dfif.setRepository(new File(tempPath));
dfif.setSizeThreshold(1024*10); //3.获得ServletFileUpload的实例
ServletFileUpload sfu=new ServletFileUpload(dfif);
sfu.setSizeMax(1024*1024*3);//限制上传的总大小为3MB
sfu.setFileSizeMax(1024*1024);//限制每个上传的文件大小最多为1MB
//4.遍历上传列表
try {
List<FileItem>list=sfu.parseRequest(request);
Iterator<FileItem>it=list.iterator(); while(it.hasNext())
{
FileItem file=it.next();
String fileName=file.getName();
fileName=fileName.substring(fileName.lastIndexOf("\\")+1);
String fileType=file.getContentType();
InputStream is=file.getInputStream(); int hashCode=fileName.hashCode();
String dir1=Integer.toHexString(hashCode&0XF);//计算第一级目录
String dir2=Integer.toHexString((hashCode>>4)&0XF);//计算第二级目录 String aimDir=savaPath+"/"+dir1;
if(!new File(aimDir).exists())
{
new File(aimDir).mkdir();
}
aimDir=aimDir+"/"+dir2;
if(!new File(aimDir).exists())
{
new File(aimDir).mkdir();
}
// String extName=fileName.substring(fileName.lastIndexOf("."));
String savaFileName=aimDir+"/";
String extName=fileName.substring(fileName.lastIndexOf("."));
fileName=UUID.randomUUID().toString().replaceAll("-", "")+extName;
savaFileName=savaFileName+fileName;
FileOutputStream fos=new FileOutputStream(new File(savaFileName));
long fileSize=is.available();
int length=-1;
byte []buff=new byte[1024*1024];
while((length=is.read(buff))!=-1){
fos.write(buff, 0, length);
}
fos.close();
is.close();
file.delete();//清空缓存文件 pw.println("文件上传成功!");
pw.println("<br/>文件名:"+fileName);
pw.println("<br/>文件达小:"+fileSize);
pw.println("<br/>文件类型:"+fileType);
if(fileType.contains("image"))
{
pw.println("<br/>图片:");
String temp=getServletContext().getContextPath()+"/upload/"+dir1+"/"+dir2+"/"+fileName;
pw.println("<br/><img width='500' src='"+temp+"'/>");
}
pw.println("<hr/>");
}
} catch (FileUploadException e) {
e.printStackTrace();
}
}
}

CatalogBreakUp.java

  (5)效果演示

    开始上传:

    

    上传结果:

    

    服务器查看文件

     

    

    

  三张图片被打散放到了三个目录中。

  (6)为什么显示的文件名改变了

    如果想要将图片在浏览器上显示出来,由于是中文不能直接拿过来用,因为会被浏览器编码,所以要进行文件的重命名。

    使用文件重命名有两个好处:

    【1】防止文件名相同的情况,事实上文件名相同的情况太常见了,一定要保证即使文件名相同也要将文件成功上传到服务器。

    【2】将文件名设置成为字母和数字混合的字符串的形式可以防止中文不能被浏览器识别的情况

7.如果要求上传文件,并且同时还要上传一些文字说明及其他信息怎么办?

  使用FileItem类解决这个问题。

如果你上传是一普通的文本元素,则可以通过以下方式获取元素中的数据

<form enctype=”multipart/form-data”>

    <input type=”text” name=”name”/>

String

getString()  用于获取普通的表单域的信息。
          Returns the
contents of the file item as a String, using the default character encoding.(IOS-8859-1)

String

getString(String encoding) 可以指定编码格式
          Returns the
contents of the file item as a String, using the specified encoding.

void

write(File file) 直接将文件保存到另一个文件中去。
          A convenience
method to write an uploaded item to disk.

以下文件用判断一个fileItem是否是file(type=file)对象或是text(type=text|checkbox|radio)对象:

 boolean

isFormField()  如果是text|checkbox|radio|select这个值就是true.
          Determines
whether or not a FileItem instance represents a simple form field.

最后:小练习源代码:https://github.com/kdyzm/day22_1

补充:当存在普通文本框类型的时候如何区分上传的文本框和普通的文本框?

使用方法:FileItem.isFormField(),如果不是文件上传,则返回值是true,否则是false。

这里结合beanutils更加方便的封装请求的数据。

public String addOneBook(HttpServletRequest request,HttpServletResponse response) throws IOException
{
//1.获得保存上传文件的目录的路径以及缓存文件的目录
String tempPath=this.getServletContext().getRealPath("temp");
//2.设置磁盘文件项目工厂类的实例并设置缓存目录以及文件大小设置
DiskFileItemFactory dfif=new DiskFileItemFactory();
dfif.setRepository(new File(tempPath));
dfif.setSizeThreshold(1024*10);
//3.获得ServletFileUpload的实例
ServletFileUpload sfu=new ServletFileUpload(dfif);
sfu.setSizeMax(1024*1024*3);//限制上传的总大小为3MB
// sfu.setFileSizeMax(1024*1024);//限制每个上传的文件大小最多为1MB
//4.遍历上传列表
try {
List<FileItem>list=sfu.parseRequest(request);
Iterator<FileItem>it=list.iterator();
Book book=new Book();
Map<String,String[]> map=new HashMap<String,String[]>();
List<String>booktypelist=new ArrayList<String>();
while(it.hasNext())
{
FileItem file=it.next();
if(file.isFormField())//如果不是文件上传,是普通的文本文件
{
String fileName=file.getFieldName();
if(fileName.equals("booktype"))
{
booktypelist.add(file.getString());
continue;
}
String value=file.getString();
value=new String(value.getBytes("iso-8859-1"),"utf-8");
map.put(fileName,new String[]{value});
}
else
{
String fileName=file.getFieldName();
String value=getUploadImageName(file);
map.put(fileName,new String[]{value});
}
}
BeanUtils.populate(book, map);
System.out.println(book);
//这里需要开启事务进行事务的管理。
Book boo=bookService.addNewBook(book,booktypelist);
if(boo==null)//如果添加图书失败
{
System.out.println("添加新图书失败!");
return null;
}
else//如果添加图书成功,则跳转到详细信息页面进行显示
{
request.setAttribute("book", book);
List<BookType>btls=bts.getBookTypeByBookid(book.getId());
request.setAttribute("booktypes", btls);
return "showOneBookInfo";
}
}catch (Exception e) {
e.printStackTrace();
}
return null;
}

【Java EE 学习 22 上】【文件上传】【目录打散】【文件重命名】的相关教程结束。

《【Java EE 学习 22 上】【文件上传】【目录打散】【文件重命名】.doc》

下载本文的Word格式文档,以方便收藏与打印。