文件上传详解

一、文件上传

1.文件上传的原理分析
1.1文件上传的必要前提:
a.表单的method必须是post
b.表单的enctype属性必须是multipart/form-data类型的 

enctype默认值:application/x- www-form-urlencoded  
作用:告知服务器,请求正文的MIME类型 
urlencoded的正文的格式为:
application/x- www-form-urlencoded  : username = abc &passwoard = 123,

String name = request.getParameter("name");  这种获取参数的方法,仅适用于该种类型

1.2将表单设置为enctype = multipart/form-data类型之后,内容的展现类型如下所示
文件头和正文之间用空行隔开,上面是指定的分界符号,用来区分不同的表单提交内容。


1.3文件上传的原理:
  解析请求正文的内容

c.表单中提供type="file"类型的上传组件 

2.借助第三方组件实现文件上传

拷贝jar包:commons-fileupload.jar   commons-io.jar 具体参考

http://commons.apache.org/proper/commons-fileupload/using.html

2.2How it works


fileupload组件的工作流程如下:

 


借助第三方组件,Commons-fileupload组件来实现文件的上传。
package com.itheima.servlet;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
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;
import org.apache.commons.io.FilenameUtils;

import com.sun.corba.se.impl.orb.ParserTable;


//借助commons-fileupload 实现文件上传的简单案例
public class UploadServlet2 extends HttpServlet {

 public void doGet(HttpServletRequest request, HttpServletResponse response)
   throws ServletException, IOException {
  //设置响应正文的编码格式,防止中文乱码
  response.setContentType("text/html;charset=UTF-8");
  PrintWriter out = response.getWriter();
 
  //判断用户提交的正文类型是不是multipart/form-data类型
  boolean isMultipart = ServletFileUpload.isMultipartContent(request);
  if(!isMultipart){
   throw new RuntimeException("请检查您的表单的enctype属性,确定是multipart/form-data");
  }
 
  //创建DiskFileItemFactory
  DiskFileItemFactory  dfif = new DiskFileItemFactory();
  ServletFileUpload parser = new ServletFileUpload(dfif);
 
  List<FileItem> items = null ;
 
  //解析内容
  try {
   items = parser.parseRequest(request);
  } catch (FileUploadException e) {
   throw new RuntimeException("解析上传内容失败,请重新试一下");
  }
 
  //处理请求内容
  if(items != null){
   for(FileItem  item:items){
    if(item.isFormField()){
     //普通表单字段
     processFormField(item);
    }
    else{
      //上传的文件字段
     processUploadField(item);
    }
   }
   
   out.write("上传成功");
  }
 
 
 }
 
 //上传字段
 private void processUploadField(FileItem item) {
  try {
// InputStream  in = item.getInputStream(); //输入流
   String fileName = item.getName(); //上传文件的名称  C:\Users\zhangjianbo\Desktop\a.txt    a.txt
   
   //截取文件名
// fileName = fileName.substring(fileName.lastIndexOf("\\")+1);
   if(fileName!=null){
    fileName = FilenameUtils.getName(fileName);
   }
   
   //把文件存到服务器的某个目录中
   String storeDirectoryPath = getServletContext().getRealPath("/files");
   
   File storeDirectory = new File(storeDirectoryPath);
   if(!storeDirectory.exists()){
    storeDirectory.mkdirs();
   }
   
// //构建输出流,输出流需要指定文件写出的路径  
// OutputStream out  = new FileOutputStream(storeDirectoryPath +File.separator+ fileName);
// int len = -1;
// byte b[] = new byte[1024];
// while((len = in.read(b))!=-1){
// out.write(b,0,len);
// }
//
// in.close();
// out.close();
//item.delete();  //传递大文件的时候,会自动产生临时文件 
   
   item.write(new File(storeDirectoryPath +File.separator+ fileName));// File Item中存在write方法
  } catch (Exception e) {

   throw new RuntimeException("上传失败,请重试");
  }
 
 
 
 }
 //普通表单字段
 private void processFormField(FileItem item) {
  String filedName = item.getFieldName(); //字段名
   String filedValue = item.getString(); //字段值  

   System.out.println(filedName + "=" + filedValue);
 
 }

 public void doPost(HttpServletRequest request, HttpServletResponse response)
   throws ServletException, IOException {
  doGet(request, response);
 }

}




3.文件上传详解

3.1.  DiskFileItemFactory

该类主要是对磁盘文件的操作,对于上传文件分为两部分:
1)如果文件的大小小于10KB,那么默认存放到缓存中。
2)如果文件的大小大于10KB,那么该文件将存放到临时文件的存放目录中,默认是当前登录用户的临时文件目录。

 

public void setRepository(File repository):设置临时文件的存放目录

public void setSizeThreshold(int sizeThreshold):设置缓存的大小


专题:关于临时文件



虚拟内存的概念:当内存中的空间不够用的时候,这个时候,会在磁盘上划分一部分区域作为虚拟内存,将内存中不经常使用的文件先保存到虚拟内存中。所以虚拟内存是磁盘上的一部分存储空间。


为什么启用缓存呢?减少与硬盘的交互次数,最终还是要存放到磁盘上的 ,存放到内存中,速度更快。 所以上传的文件首先是存放到内存中的缓存中的,


如果是一个100M大小的文件,那么每次获取10Kb,到内存中,然后存放到临时文件中,当所有的100M都存放到临时文件中后,然后再把临时文件中的文件全部放到最终文件中,最后把临时文件进行删除。 



临时文件需要在代码中指定来实现,调用item.delete()来删除。  FileItem item  =  new DiskFileItemFactory()

临时文件的删除问题:

文件上传时,自己用IO流处理的文件,一定要在流关闭的时候删除临时文件,FileItem.delete()

建议使用:FileItem.writer(File f)会自动删除临时文件


3.2乱码问题 

1) 普通字段乱码

filedValue = item.getString("UTF-8");  如果提交的是输出框的值,那么需要通过FileItem.getString(String charset);编码需要和客户端一致

2)文件名乱码 

可以通过requset.setCharacterEncoding("UTF-8");仅能够解决报文中的请求头的问题,不能解决正文的编码问题 

3)解决文件名重名的问题 

通过UUID来命名文件名fileName = UUID.randomUUID()+"_"+   FilenameUtils.getName(fileName);

4)保证服务器的安全

为了防止用户上传的文件,通过路径可以直接访问到该文件,造成服务器的漏洞,bat文件等,需要将用户上传的文件存放到用户不能直接访问的地方,如:

String storeDirectoryPath = getServletContext().getRealPath("/WEB-INF/files"); 将文件放到WEB-INF目录下面。 

5)避免一个文件夹中的文件过多 

解决方法:分目录存储

参考实现:

1)按照日期分目录存储 

2)通过文件名的HashCode来设定文件夹

 //上传字段

 private void processUploadField(FileItem item) {

  try {

   String fileName = item.getName(); //上传文件的名称  C:\Users\zhangjianbo\Desktop\a.txt    a.txt

   

   //截取文件名

   if(fileName!=null){

    fileName = UUID.randomUUID()+"_"+   FilenameUtils.getName(fileName);

   }

   

// //分目录存储:日期解决方案

// Date now = new Date();

// DateFormat df = new SimpleDateFormat("yyyy-MM-dd");

   

// String time = df.format(now);

   

   //通过文件名的hashCode来实现

   String childDiretory = makeChildDirectory(getServletContext().getRealPath("/WEB-INF/files/"),fileName);

   //把文件存到服务器的某个目录中

   String storeDirectoryPath = getServletContext().getRealPath("/WEB-INF/files/"+childDiretory);

   

   File storeDirectory = new File(storeDirectoryPath);

   if(!storeDirectory.exists()){

    storeDirectory.mkdirs();

   }

   item.delete();//表示删除临时文件

   item.write(new File(storeDirectoryPath +File.separator+ fileName));// File Item中存在write方法

  } catch (Exception e) {

   throw new RuntimeException("上传失败,请重试");

  }

 

 

 

 }

 private String makeChildDirectory(String realPath, String fileName) {

 

  int hashCode = fileName.hashCode();

  int dir1 = hashCode&0xf;//取文件名的二进制的1~4位

  int dir2 = (hashCode&0xf0)>>4; //获取5~8

 

  String directory = "" + dir1 + File.separator +dir2;

  File file = new File(realPath,directory);

 

  if (!file.exists()){

   file.mkdirs();

  }

  return directory;

 

 

 

 }

3.6 限制文件上传的大小,并给出友好提示 

Web方式不适合传输较大的文件

1)单文件大小(一般限制4M)

 //创建DiskFileItemFactory

  DiskFileItemFactory  dfif = new DiskFileItemFactory();

  ServletFileUpload parser = new ServletFileUpload(dfif);

 

  parser.setFileSizeMax(4*1024*1024); //设置单个文件上传的大小

  parser.setSizeMax(6*1024*1024); //多文件上传时的总大小限制

 

 

 

  List<FileItem> items = null ;

 

  //解析内容

  try {

   items = parser.parseRequest(request);

  }catch(FileUploadBase.FileSizeLimitExceededException e){  //该异常为一个静态内部类

   out.write("上传的文件超出了4M");

   return;

  }

  catch(FileUploadBase.SizeLimitExceededException e){  //该异常为一个静态内部类

   out.write("总文件大小超出了6M");

   return;

  }

  catch (FileUploadException e) {

   throw new RuntimeException("解析上传内容失败,请重新试一下");

  }

2)总文件大小(多文件上传)

  parser.setSizeMax(6*1024*1024); //多文件上传时的总大小限制 


3.7 限制上传文件的类型 

通过扩展名+ 文件的MIME类型来进行限制 


// 扩展名

    String extension = FilenameUtils.getExtension(fileName);

    // MIME类型 -- 判断MIME类型仅限于IE浏览器

    String contentType = item.getContentType();

    if (contentType.startsWith("image/")) {


MIME类型:Web请求文件中的Content-Type类型,就表示MIME类型

大类型/小类型组成,我们只需要判断大类型即可。




4.文件的下载 
 public void doGet(HttpServletRequest request, HttpServletResponse response)
   throws ServletException, IOException {
 
  String uuidfilename = request.getParameter("filename");//get方式提交的
  uuidfilename = new String(uuidfilename.getBytes("ISO-8895-1"),"UTF-8");//UUID的文件名
 
  String storeDirectory = getServletContext().getRealPath("/WEB-INF/files");
  //得到存放的子目录
  String childDirecotry = makeChildDirectory(storeDirectory,uuidfilename);
 
  //构建输出流
  InputStream in  = new FileInputStream(storeDirectory + File.separator + childDirecotry + File.separator + uuidfilename);
 
  //下载
  String oldfilename = uuidfilename.substring(uuidfilename.indexOf("_") + 1);
 
  //通知客户端以下载的方式打开
  response.setHeader("Content-Disposition", "attachment;filename="+URLEncoder.encode(oldfilename,"UTF-8"));
 
  OutputStream  out = response.getOutputStream();
 
  int len = -1;
  byte b[] = new byte[1024];
  while((len = in.read(b))!= - 1){
   out.write(b,0,len);
  }
   
  in.close();
  out.close();
 
 }

 
 private String makeChildDirectory(String realPath, String fileName) {

  int hashCode = fileName.hashCode();
  int dir1 = hashCode & 0xf;// 取文件名的二进制的1~4位
  int dir2 = (hashCode & 0xf0) >> 4; // 获取5~8

  String directory = "" + dir1 + File.separator + dir2;
  File file = new File(realPath, directory);

  if (!file.exists()) {
   file.mkdirs();
  }
  return directory;

 }
 
 
 public void doPost(HttpServletRequest request, HttpServletResponse response)
   throws ServletException, IOException {
  doGet(request, response);
 }

}



=================jstl url ==================
       
       
       <h1>jstl中的url的编码方式,使用url编码,因为路径中有中文</h1>
       <c:forEach items="${map}" var = "me">
        <c:url  value = "/servlet/DownLoadServlet" var = "url">
         <c:param name="filename" value="${me.key}">
          </c:param>
         
         
        </c:url>
       ${me.value}&nbsp;&nbsp;<a href="${url}"></a>下载<br/>




==============showallfiles=============
public class ShowAllFilesServlet extends HttpServlet {

 public void doGet(HttpServletRequest request, HttpServletResponse response)
   throws ServletException, IOException {
  String storeDirectory =  getServletContext().getRealPath("/WEB-INF/files");
  File root = new File(storeDirectory);
 
 
  //使用Map保存递归的文件名,key:UUID文件名  value:老文件名
  Map<String,String> map = new HashMap<String, String>();
  treeWalker(root,map);
 
  request.setAttribute("map", map);
  request.getRequestDispatcher("/listFiles.jsp").forward(request, response);
 }
 //递归,将文件名存放到Map中
 private void treeWalker(File root, Map<String, String> map) {
  if(root.isFile()){
   String fileName = root.getName(); //获取当前的文件名   234322_a_a.bmp
   String oldFileName = fileName.substring(fileName.indexOf("_") + 1);
   map.put(fileName, oldFileName);
   
   
  }
  else{
   File fs[]  = root.listFiles();
   for(File file:fs){
    treeWalker(file, map);
   }
  }
 
 
 }

二、Servlet规范中的监听器
1.观察者设计模式
2.Servlet规范中提供的八个监听器
3.监听器案例:查看在线登录的人数,踢人



  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值