[TOC]

l 文件上传和下载

文件的上传:

文件上传的概述:

什么是文件上传:

文件上传:
QQ上传头像.将文件存入到网盘中…
将本地文件写到服务器上.

文件上传的三要素:(*)

文件上传的三要素:
* 表单的提交的方式必须是POST方式:
* GET方式有大小的限制的.POST方式没有大小的限制.
* 表单中需要有文件上传项:
* 在表单中需要有.而且文件上传项必须有name的属性.
* 表单的enctype属性:必须设置为multipart/form-data

文件上传的原理分析:

  • 没有设置enctype属性的情况:
    Host: localhost
    User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:57.0) Gecko/20100101 Firefox/57.0
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,/;q=0.8
    Accept-Language: zh-CN,en-US;q=0.8,zh;q=0.5,en;q=0.3
    Accept-Encoding: gzip, deflate
    Referer: http://localhost/upload/jsp/demo1.jsp
    Content-Type: application/x-www-form-urlencoded
    Content-Length: 27
    Cookie: JSESSIONID=F34C03C793EA7D62E2D1328F86A9257F
    Connection: keep-alive
    Upgrade-Insecure-Requests: 1

    desc=aaa.txt&upload=aaa.txt
    =========================enctype属性为默认值的情况:没有文件中的内容.


    * 设置了enctype属性为multipart/form-data
    Host: localhost
    User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:57.0) Gecko/20100101 Firefox/57.0
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,/;q=0.8
    Accept-Language: zh-CN,en-US;q=0.8,zh;q=0.5,en;q=0.3
    Accept-Encoding: gzip, deflate
    Referer: http://localhost/upload/jsp/demo1.jsp
    Content-Type: multipart/form-data; boundary=—————————————-41184676334
    Content-Length: 295
    Cookie: JSESSIONID=F34C03C793EA7D62E2D1328F86A9257F
    Connection: keep-alive
    Upgrade-Insecure-Requests: 1
    ——————————————-41184676334
    Content-Disposition: form-data; name=”desc”

    aaa.txt
    ——————————————-41184676334
    Content-Disposition: form-data; name=”upload”; filename=”aaa.txt”
    Content-Type: text/plain

    hello world…
    ——————————————-41184676334—

    文件上传的技术:

    JSPSmartUpload :jspSmartUpload组件是应用JSP进行B/S程序开发过程中经常使用的上传下载组件,它使用简单,方便。现在我又为其加上了下载中文名字的文件的支持,真个是如虎添翼,必将赢得更多开发者的青睐。

    FileUpload :FileUpload 是 Apache commons下面的一个子项目,用来实现Java环境下面的文件上传功能,与常见的SmartUpload齐名.

    Servlet3.0 :提供了文件上传的功能.

    Struts2 :提供了文件上传和下载的功能.

    文件上传的入门案例:

    步骤一:创建一个web项目,引入相应jar包:

  • 引入jar包:
    commons-fileupload-1.2.1.jar
    commons-io-1.4.jar

    步骤二:创建一个页面:

  • 满足文件上传的条件:

    文件上传的页面




    文件描述:

    文件上传:


    步骤三:编写Servlet:

    public class UploadServlet extends HttpServlet {

    public void doGet(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
    /InputStream is = request.getInputStream();
    String info = IOUtils.toString(is);
    System.out.println(info);
    /
    /**
    * 使用FileUpload组件完成文件上传:
    创建一个磁盘文件项工厂:
    创建一个核心解析类:
    使用核心解析类解析request请求.返回一个List集合.封装的一个FileItem的对象.
    遍历List集合获得到表单中的每个部分(FileItem).
    判断FileItem是否是文件上传项:
    文件上传项:完成文件上传.
    普通项:获得普通项名称和值.
    */
    // 创建磁盘文件项工厂:
    DiskFileItemFactory diskFileItemFactory = new DiskFileItemFactory();
    // 创建核心解析类:
    ServletFileUpload servletFileUpload = new ServletFileUpload(diskFileItemFactory);
    // 解析request请求:
    try {
    List list = servletFileUpload.parseRequest(request);
    // 遍历集合获得到每个部分:
    for (FileItem fileItem : list) {
    // 判断是否是文件上传项:
    if(fileItem.isFormField()){
    // 普通项
    String name = fileItem.getFieldName();
    String value = fileItem.getString();
    System.out.println(name+” “+value);
    }else{
    // 文件上传项
    // 获得文件上传的路径:
    String path = this.getServletContext().getRealPath(“/upload”);
    // 获得文件名称:
    String fileName = fileItem.getName();
    // 获得文件的内容:
    InputStream is = fileItem.getInputStream();
    // 创建一个输出流:
    OutputStream os = new FileOutputStream(path+”\“+fileName);
    // 两个流完成对接:
    byte[] b = new byte[1024];
    int len = 0;
    while((len = is.read(b))!=-1){
    os.write(b, 0, len);
    }
    is.close();
    os.close();
    }
    }
    } catch (FileUploadException e) {
    e.printStackTrace();
    }
    }

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

    doGet(request, response);
    }

    }

    FileUpload中的核心API:

    DiskFileItemFactory:磁盘文件项工厂:

    构造方法:
    文件上传和下载 - 图1
    无参数的构造方法
    文件上传和下载 - 图2
    带有参数的构造方法:
    参数:
    int sizeThreshold :FileUpload组件的缓冲区大小.默认是10kb.
    File repository :文件上传过程中产生临时文件存放的路径.

    方法:
    文件上传和下载 - 图3
    设置FileUpload的组件的文件上传的缓冲区.
    文件上传和下载 - 图4
    * 设置临时文件存放的路径.

    ServletFileUpload:核心解析类:

    构造方法:
    文件上传和下载 - 图5

    方法:
    文件上传和下载 - 图6
    是用来判断表单的enctype的属性是否是multipart/form-data.
    文件上传和下载 - 图7
    处理中文文件名的上传的乱码问题:
    文件上传和下载 - 图8
    解析request请求:返回一个List集合.List集合中封装的FileItem的对象.
    文件上传和下载 - 图9
    设置单个文件上传大小:
    文件上传和下载 - 图10
    设置表单中的所有文件上传的总大小:
    文件上传和下载 - 图11
    设置监听器:作用监听上传的进度.

    FileItem:文件项.

    方法:
    文件上传和下载 - 图12
    判断表单的元素是普通项还是文件上传项.普通项返回true.如果是文件上传项返回false.
    文件上传和下载 - 图13
    获得普通项的名称:
    文件上传和下载 - 图14
    获得普通项的值:
    getString();
    getString(String encoding); ——用来解决普通项的中文乱码的问题.
    文件上传和下载 - 图15
    获得文件上传项的文件的名称.
    文件上传和下载 - 图16
    获得文件上传项的文件的内容.
    文件上传和下载 - 图17
    删除文件上传中的临时文件的.

    通过JS控制多文件的上传:

    使用JS添加节点移除节点:

    JS的代码:

    文件上传过程中的问题的解决:

    问题一:IE浏览器兼容的问题:

    • IE6浏览器版本比较老:
      获取文件的名称: D:\伟创聚赢javaEE\PPT课程\aaa.txt
      如何解决获得文件名的问题:
      查找最后一个 \ 的位置:
      从这个位置向后截取字符串:
      if(fileName.lastindexOf(“\“) != -1){
      // 说明浏览器的版本比较低:
      int idx = fileName. lastindexOf(“\“);
      fileName = fileName.substring(idx + 1);
      }

      问题二:上传文件的文件名的重名的问题:

    • 张三和李四都要去上传一个文件:aaa.txt
      张三的aaa.txt:hello world…
      李四的aaa.txt:我是李四…
      * 后上传的文件就会将先上传的问题覆盖了.

      使用唯一文件名:(使用随机的字符串作为文件名.)
      在Java中如何生存一个不重复的随机的字符串?
      UUID :UUID.randomUUID();

      抽取一个获得唯一文件名工具类:
      public class UUIDUtils {

      /
      生成唯一文件名的方法:
      @param fileName:真实文件名.
      @return
      /
      public static String getUUIDFileName(String fileName){
      return
      UUID.randomUUID().toString().replace(“-“, “”)+”_”+fileName**;
      }
      }

      问题三:一个目录下存放的文件过多的问题.

    • 用户量特别的大.每个用户又会上传很多文件.将所有的文件存放到了一个路径下.
      如果文件过多,打开这个文件夹的时候,就已经很慢.更别说是读写.

      解决问题的办法:
      将多个文件进行分离:(目录的分离).
      按用户划分:
      一个用户创建一个文件夹.
      按时间划分:
      一个月创建一个文件夹.一个星期一个文件夹.一天一个文件夹…
      按个数划分:
      一个文件夹存放3000个文件.
      **
      按目录分离的算法划分:
      按照某种算法将文件进行分离.

      目录分离的算法的分析:
      获得文件的唯一文件名://aaa.txt 4617c7ab98ea454b80044cf6c1856089_aaa.txt 目录:upload
      获得到唯一文件名的hashCode().
      哈希值是一个int类型的值.int类型是4个字节.每个字节8位.(32位).
      哈希值 & 0xf;得到一个值.用这个值作为一级路径.
      哈希值右移4位&0xf;得到一个值.用这个值作为二级路径.


      * 目录分离的工具类的代码实现:
      public class UploadUtils {

      public static String getPath(String uuidFileName){
      /

      1.获得文件的唯一文件名:
      2.通过唯一文件名.获得其的hashCode值.
      3.hashCode & 0xf; 得到值 作为一级目录.
      4.hashCode值右移4位 & 0xf;得到值 作为二级目录.
      5.依次执行该操作.
      /
      // 获得唯一文件名的hashCode:
      int code1 = uuidFileName.hashCode();
      // & 0xf;
      int d1 = code1 & 0xf;// 作为一级目录.
      // code1 右移>> 4位.
      int code2 = code1 >>> 4;
      // code2 & 0xf;
      int d2 = code2 & 0xf;// 作为二级目录.
      return “/“+d1+”/“+d2;
      }
      }

      文件下载:

      文件下载的概述:

      什么是文件下载:

      文件下载 :将服务器上文件下载到本地计算机.
      从网盘中下载文件.
      下载音乐,电影.

      文件下载两种形式:

      文件下载有两种方式:
      一种:超链接下载.
      对于下载的资料.将资源写到下载.
      前提:如果浏览器支持这个格式的文件.可以在浏览器中打开.如果浏览器不支持这个格式的文件.提示下载.
      **
      二种:手动编写代码的方式进行下载.
      *
      无论浏览器是否支持该格式的文件:提示下载.
      *
      设置两个头和一个流:
      *
      Content-Type:代表的是文件的MIME类型.
      *
      Content-Disposition:代表文件需要以下载的形式打开.
      *
      InputStream:代表的文件的输入流.
      *
      因为输出流是固定的:response.getOutputStream();**

      超链接的方式完成文件下载:

      使用超链接的方式完成文件下载


      文件下载


      tu1.zip

      tu2.jpg

      文件下载的入门案例:

      手动编码的方式完成文件的下载:

      public class DownloadServlet extends HttpServlet {

      public void doGet(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {
      /
      1.接收文件名.
      2.完成文件的下载:
      获得download路径下的该文件:获得download路径的磁盘绝对路径.
      设置两个头和一个流:完成文件下载.
      Content-Type :MIME类型.
      Content-Dispostion :
      InputStream:
      */
      // 1.接收文件名:
      String filename = request.getParameter(“filename”);
      // 2.获得download的磁盘绝对路径.
      String path = this.getServletContext().getRealPath(“/download”);
      // 3.设置两个头和一个流:
      // 动态获得文件的MIME的类型:
      String mimeType = this.getServletContext().getMimeType(filename);
      response.setContentType(mimeType);
      // 设置Content-Dispostion: 如果文件是浏览器能够解析的格式,浏览器也不直接打开文件
      response.setHeader(“Content-Disposition”, “attachment;filename=”+filename);
      // 设置文件的输入流InputStream:
      InputStream is = new FileInputStream(path+”\“+filename);
      // 获得一个向浏览器输出的一个输出流:
      OutputStream os = response.getOutputStream();**
      byte[] b = new byte[1024];
      int len = 0;
      while((len = is.read(b))!=-1){
      os.write(b, 0, len);
      }
      is.close();
      }

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

      doGet(request, response);
      }

      }

      文件下载的综合案例:

      案例的需求:

    • 给定任意个的一个盘符下的路径:
      * 将这个路径中的所有的文件在页面中列出并提供下载的功能.

      实现列表显示任意路径下的所有文件:(树形结构的遍历.)

      代码实现树形结构的遍历:

      <%
      // 创建一个队列:
      Queue queue = new LinkedList();
      // 将跟节点先入队:
      File root = new File(“D:\download”);
      queue.offer(root);
      // 遍历队列:如果队列不为空.
      while(!queue.isEmpty()){
      // 将跟节点出队:获得其所有的子节点.
      File file = queue.poll();
      // 获得其所有的子节点
      File[] files = file.listFiles();
      // 判断其子节点是否为叶子节点:
      for(File f:files){
      if(f.isFile()){
      // 如果是叶子节点:显示到页面
      %>

      <%= f.getName() %>


      <%
      }else{
      //
      如果不是叶子节点:将该节点入队.
      queue.offer(f);
      }
      }
      }

      %>

      综合案例的后台下载的代码的实现:

      public class DownloadListServlet extends HttpServlet {

      public void doGet(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {
      /
      1.获得提交的参数:
      2.获得到文件名:
      3.提供文件下载的功能:
      设置Content-Type:
      设置Content-Dispositon
      提供文件的输入流:
      /
      // 获得提交的参数:
      String path = new String(request.getParameter(“path”).getBytes(“ISO-8859-1”),”UTF-8”);
      System.out.println(path);
      // 获得文件名:
      int idx = path.lastIndexOf(“\“);
      String filename = path.substring(idx + 1);
      // 提供文件下载的功能:
      // 获得文件的MIME的类型:
      String mimeType = this.getServletContext().getMimeType(filename);
      // 设置Content-Type
      response.setContentType(mimeType);
      // 设置Content-Dispostion:
      // 对于中文文件名的处理办法:
      // 如果是中文的文件:不同的浏览器是有不同的编码的格式的:
      // IE浏览器对中文文件使用的URL编码.Firefox浏览器对中文文件采用的是Base64编码.
      // 判断不同的浏览器采用不同的编码的格式:
      // User-Agent:包含了客户端的浏览器的信息.
      String agent = request.getHeader(“User-Agent”);
      // System.out.println(agent);
      if(agent.contains(“Firefox”)){
      // 火狐浏览器:
      filename = base64EncodeFileName(filename);
      }else{
      // IE浏览器:
      filename = URLEncoder.encode(filename, “UTF-8”);
      }
      response.setHeader(“Content-Disposition”, “attachment;filename=”+filename);
      // 提供文件的输入流:
      InputStream is = new FileInputStream(path);
      // 提供输出流:
      OutputStream os = response.getOutputStream();
      // 两个流对接:
      byte[] b = new byte[1024];
      int len = 0;
      while((len = is.read(b))!=-1){
      os.write(b, 0, len);
      }
      is.close();
      }

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

      doGet(request, response);
      }

      /

      Base64对文件名进行编码:
      @param fileName
      @return
      /
      public String base64EncodeFileName(String fileName) {
      BASE64Encoder base64Encoder = new BASE64Encoder();
      try {
      return “=?UTF-8?B?”
      + new String(base64Encoder.encode(fileName
      .getBytes(“UTF-8”))) + “?=”;
      } catch (UnsupportedEncodingException e) {
      e.printStackTrace();
      throw new RuntimeException(e);
      }
      }
      }