首页 星云 工具 资源 星选 资讯 热门工具
:

PDF转图片 完全免费 小红书视频下载 无水印 抖音视频下载 无水印 数字星空

Java SE 文件上传和文件下载的底层原理

编程知识
2024年07月27日 16:40

1. Java SE 文件上传和文件下载的底层原理

@

目录


2. 文件上传

  1. 文件的上传和下载,是常见的功能。说明:这里我们的文件上传仅仅只是对于小文件上的上传 。如果是传输大文件,一般用专门工具或者插件。

文件上传下载需要使用到如下两个包,需要导入。同时记得要进行导入加载到项目当中去。

在这里插入图片描述

在这里插入图片描述

文件上传原理示意图:

在这里插入图片描述

在这里插入图片描述

文件上传的解读:

  1. 文件上传还是使用的是表单 的方式提交
  2. 其中 action 还是按照以前规定来指定
  3. method 指定为 post ,因为文件上传是比较大的文件, get 无法发送较大的文件。
  4. enctype:encodetype 编码类型,要设置为:multipart/form-data ,表示进行二进制文件的提交,multipart/form-data: 表示表单提交的数据是有多个部分组成,也就是可以提交二进制数据和文本数据,两者都行。在这里插入图片描述
  1. 注意:enctype:encodetype 默认是:enctype="application/x-www-form-urlencoded" 即为 URL 编码,这种编码方式不适合对二进制文件数据的提交,一般适用于文本数据的提交。

操作上传文件流程:

  1. 判断是不是一个文件表单
  2. 判断表单提交的各个表单项是什么类型
  3. 如果是一个普通的表单项,就按照文本的方式来处理。
  4. 如果是一个文件表单项(二进制数据),使用 IO技术进行处理。
  5. 把表单提交的文件数据,保存到你指定的服务端的某个目录。

2.1 文件上传应用实例

在这里插入图片描述

对应文件上传的前端页面代码 : jsp

在这里插入图片描述

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <base href="<%=request.getContextPath()+"/"%>>">
    <style type="text/css">
        input[type="submit"] {
            outline: none;
            border-radius: 5px;
            cursor: pointer;
            background-color: #31B0D5;
            border: none;
            width: 70px;
            height: 35px;
            font-size: 20px;
        }

        img {
            border-radius: 50%;
        }

        form {
            position: relative;
            width: 200px;
            height: 200px;
        }

        input[type="file"] {
            position: absolute;
            left: 0;
            top: 0;
            height: 200px;
            opacity: 0;
            cursor: pointer;
        }
    </style>
    <script type="text/javascript">
        function prev(event) {
//获取展示图片的区域
            var img = document.getElementById("prevView");
//获取文件对象
            let file = event.files[0];
//获取文件阅读器
            let reader = new FileReader();
            reader.readAsDataURL(file);
            reader.onload = function () {
//给 img 的 src 设置图片 url
                img.setAttribute("src", this.result);
            }
        }
    </script>
</head>
<body>
<!-- 表单的 enctype 属性要设置为 multipart/form-data -->
<form action="fileUploadServlet" method="post" enctype="multipart/form-data">
    家居图: <img src="2.jpg" alt="" width="200" height="200"> <input type="file" name="pic"
                                                                                value="2xxx.jpg" onchange="prev(this)"/>
    家居名: <input type="text" name="name"><br/> <input type="submit" value="上传"/>
</form>
</body>
</html>

对应文件上传的Servlet 的编写。

在这里插入图片描述

package com.rainbowsea.servlet;


import com.rainbowsea.utils.WebUtils;
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 javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;

import java.nio.file.attribute.FileTime;
import java.util.List;
import java.util.UUID;

public class FileUploadServlet extends HttpServlet {

    /*
    1. 判断是不是一个文件传单
    2. 判断表单提交的各个表单项是什么类型
    3. 如果是一个普通的表单项,就按照文本的方式来处理
    4. 如果是一个文件表单项(二进制数据),使用 IO技术进行处理
    5. 把表单提交的文件数据,保存到你指定的服务端的某个目录
     */
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException,
            IOException {
        //System.out.println("被调用了");


        // 1. 判断是不是 文件表单(enctype="multipart/form-data")
        if (ServletFileUpload.isMultipartContent(request)) {
            //System.out.println("OK");
            // 2. 创建 DiskFileItemFactory 对象,用于构建一个解析上传数据的工具对象
            DiskFileItemFactory diskFileItemFactory = new DiskFileItemFactory();
            // 3. 创建一个解析上传数据的工具对象
            ServletFileUpload servletFileUpload = new ServletFileUpload(diskFileItemFactory);

            // 4. 关键的地方 servletFileUpload 对象可以把表单提交的数据 text/ 文件
            // 将其封装到 FileItem 文件项中
            // 韩老师的编程心得体会:如果我们不知道一个对象是什么结构
            // 可以:1.输出该对象,2 debug 测试
            try {
                List<FileItem> list = servletFileUpload.parseRequest(request);
                //System.out.println("List ==>" + list);
                // 遍历,并分别处理=> 自然思路
                for (FileItem fileItem : list) {
                    // java.lang.ClassCastException: org.apache.commons.fileupload.disk.DiskFileItem cannot be cast to java.nio.file.attribute.FileTime
                    //System.out.println("fileItem == >" + fileItem);
                    if (fileItem.isFormField()) { // 如果是 true 就是文本 input text
                        String name = fileItem.getString("utf-8");
                        System.out.println("图片名称: " + name);

                    } else { // 是一个文件
                        // 获取上传的文件的名字:
                        String name = fileItem.getName();
                        System.out.println("上传的文件名: " + name);

                        // 把这个上传到服务器的 temp 下的文件保存到你指定的目录
                        // 1. 指定一个目录,就是我们网站工作目录下
                        String filePath = "/upload/";

                        // 2. 获取到完整目录[io/servlet基础]
                        String fileRealPath = request.getServletContext().getRealPath(filePath);

                        System.out.println("fileRealpath = " + fileRealPath);

                        // 3. 创建这个上传的目录=> 创建目录 => Java对象
                        // 为了防止大量的目录创建,可以更加日期时间进行创建多个目录
                        File fileRealPathDirectory = new File(fileRealPath + WebUtils.getYearMonthDay());
                        if (!fileRealPathDirectory.exists()) {  // 不存在创建
                            fileRealPathDirectory.mkdirs(); // 创建

                        }

                        // 解决接收到文件名是中文乱码问题
                        servletFileUpload.setHeaderEncoding("utf-8");
                        // 4. 将文件拷贝到 fileRealPathDirectory 目录
                        // 构建一个上传文件的完整路径:目录 + 文件名
                        // 有时-》上传失败了,可能是目录的问题 ,加上 “/”
                        // 文本被替换覆盖的问题,我们也一个工具类,让文件名不重复
                        // 对上传的文件名进行处理,前面增加一个前缀,保证是唯一即可
                        name = UUID.randomUUID().toString() + "_" + System.currentTimeMillis() + "_" + name;
                        String fileFullPath = fileRealPathDirectory + "/" + name;
                        fileItem.write(new File(fileFullPath));

                        // 提示信息
                        response.setContentType("text/html;charset=utf-8");
                        response.getWriter().write("上传成功");

                    }
                }
            } catch (FileUploadException e) {
                throw new RuntimeException(e);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        } else {
            System.out.println("不是文件表单...");
        }
    }

}

上传文件操作的 Servlet 补充说明讲解:

有时-》上传失败了,可能是目录的问题 ,加上 “/”

为了防止大量的目录创建,可以增加日期时间进行创建多个目录,这样以日期天数进行创建目录的话,一年最多也就是 365个目录而已。

在这里插入图片描述

File fileRealPathDirectory = new File(fileRealPath+ WebUtils.getYearMonthDay());
String fileFullPath =  fileRealPathDirectory +"/"+ WebUtils.getYearMonthDay();

    public static String  getYearMonthDay() {
        // 如何得到当前的日期-》Java基础 日期,三代类
        LocalDateTime localDateTime = LocalDateTime.now();
        int year = localDateTime.getYear();
        int monthValue = localDateTime.getMonthValue();
        int dayOfMonth = localDateTime.getDayOfMonth();
        String yearMonthDay = year + "-" + monthValue + "-" + dayOfMonth;

        return yearMonthDay;
    }

文本被替换覆盖的问题,我们也一个工具类,让文件名不重复

// UUID.randomUUID().toString() 哈希不重复值
// System.currentTimeMillis() 获取当当前系统时间毫秒级别的
// 对上传的文件名进行处理,前面增加一个前缀,保证是唯一即可
// 同时使用特定的 "_" 符号进行分割,用于后续可能需要拿到文件名,最方便使用

name = UUID.randomUUID().toString() + "_" + System.currentTimeMillis() + "_" + name;
String fileFullPath = fileRealPathDirectory + "/" + name;

在这里插入图片描述

运行测试:看看文件是否能够上传成功

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

2.2 文件上传注意事项和细节

  1. 如果将文件都上传到一个目录下,当上传文件很多时,会造成访问文件速度变慢,因此 可以将文件上传到不同目录 比如 一天上传的文件,统一放到一个文件夹 年月日, 比如:2024-7-1 ,21001010 文件夹 。

  2. 一个完美的文件上传,要考虑的因素很多,比如断点续传、控制图片大小,尺寸,分片 上 传 , 防 止 恶 意 上 传 等 , 在 项 目 中 , 可 以 考 虑 使 用 WebUploader 组 件 ( 百 度 开 发 ) http://fex.baidu.com/webuploader/doc/index.html 在这里插入图片描述

  3. 文件上传功能,在项目中建议有限制的使用,一般用在头像、证明、合同、产品展示等, 如果不加限制,会造成服务器空间被大量占用 [比如 b 站评论,就不能传图片,微信发 1 次朋友圈最多 9 张图等..]

  4. 文件上传,创建 web/upload 的文件夹,在 tomcat 启动时,没有在 out 目录下 创建 对 应的 upload 文件夹, 原因是 tomcat 对应空目录是不会在 out 下创建相应目录的,所以,只 需在 upload 目录下,放一个文件即可, 这个是 Idea + Tomcat 的问题, 实际开发不会存 在

3. 文件下载

文件下载的原理分析图:

在这里插入图片描述

文件下载响应头说明:

  1. Content-Disposition: 表示下载的数据的展示方式,比如是内联形式(网页形式或者网页一部分)或者是文件下载方式 attachment
  2. Content-Type: 指定返回数据的类型 MIME ————》http 协议的内容

文件下载响应体说明:

  1. 在网络传输时是图片的原生数据(按照浏览器下载的编码)
  2. 这个图片时下载后查看到的,也就是浏览器本身做了解析

在这里插入图片描述

3.1 文件下载应用实例

对应文件上传的前端页面代码 : jsp

在这里插入图片描述

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>文件下载</title>
    <base href="<%=request.getContextPath()+"/"%>>">
</head>
<body>
<h1>文件下载</h1>
<a href="fileDownLoadServlet?name=java.png">点击下载Java图片</a><br/><br/>
<a href="fileDownLoadServlet?name=13-第十二章网络编程.pptx">点击下载 13-第十二章 网络编程.pptx</a><br/><br/>
</body>
</html>

注意:我们下载是,客户端从服务器端下载内容的了,所以我们需要模拟服务器,在服务器上添加上,我们客户端可以下载到的内容文件(这里:我们在 web 目录下,创建一个 download 目录,用于存放我们客户端(浏览器)可以下载到的文件)。

在这里插入图片描述

注意:创建好目录,添加好文件之后,要重新启动一下 Tomcat 服务器,让 这个我们添加的 download 资源目录,添加到 out 工作目录当中去。在这里插入图片描述

如果你重启了 Tomcat 服务器,也没有看到你创建的 download在工作目录 out下,则点击 rebuild project -> restart project

在这里插入图片描述

一个小细节:如果 web目录下创建的 目录是一个空文件夹/空目录,就是目录下没有东西的话,就算重启了 Tomcat 服务器也是不会添加到 out 目录下的。所以,只 需在 upload 目录下,放一个文件即可, 这个是 Idea + Tomcat 的问题, 实际开发不会存在

对应文件下载的Servlet 的编写。

在这里插入图片描述

package com.rainbowsea.servlet;

import org.apache.commons.io.IOUtils;
import sun.misc.BASE64Encoder;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;

public class FileDownLoadServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        System.out.println("被调用");

        // 1. 先准备要下载的文件(假定这些文件时公共的资源)
        // 重要: 保证当我们的 tomcat 启动后,在工作目录下 有 out 有 download 文件夹,并且
        // 有可供下载的文件!!
        // 再次说明:如果你没有看到你创建的 download在工作目录 out下 rebuild project -> restart proj

        // 2. 获取到要下载的文件的名字
        request.setCharacterEncoding("utf-8");
        String downLoadFileName = request.getParameter("name");
        System.out.println("downLoadFileName = " + downLoadFileName);

        // 3. 给 http 响应,设置响应头 Content-Type,就是文件的MIME
        // 通过 servletContext 来获取
        ServletContext servletContext = request.getServletContext();
        String downLoadPath = "/download/";  // 服务器资源图片,存放路径
        String downLoadFileFullPath = downLoadPath + downLoadFileName;
        String mimeType = servletContext.getMimeType(downLoadFileFullPath);
        System.out.println("mimeType = " + mimeType);
        response.setContentType(mimeType);

        // 4. 给http响应,设置响应头Content-Dispostion
        // 这里考虑的细节比较多,比如不同的浏览器写法不一样,考虑编码
        // ff 是文件名中文需要 base64, 而 ie/chrome 是 URL编码
        // 这里我们不需要同学门机制,只需知道原理
        if(request.getHeader("User-Agent").contains("Firefox")) {
            // 火狐浏览器的设置 为 Base64编码
            response.setHeader("Content-Disposition","attachment; filename==?UTF-8?B?" +
                    new BASE64Encoder().encode(downLoadFileName.getBytes("UTF-8")));
        } else {
            // 其他(主流ie/chrome) 使用 URL编码操作
            response.setHeader("Content-Disposition","attachment; filename=" +
                    URLEncoder.encode(downLoadFileName,"UTF-8"));
        }

        // 5. 读取下面的文件数据,返回给客户端
        // (1)创建一个和要下载的文件,关联的输入流
        InputStream resourceAsStream = servletContext.getResourceAsStream(downLoadFileFullPath);

        // (2) 得到返回数据的输出流{因为返回文件大多数是二进制(字节),IO Java基础}
        ServletOutputStream outputStream = response.getOutputStream();

        // (3) 使用工具类,将输入流关联的文件,对拷到输出流,并返回给客户端/浏览器
        // 注意是: import org.apache.commons.io.IOUtils; 包下的
        IOUtils.copy(resourceAsStream,outputStream);

    }
}

上传下载操作的 Servlet 补充说明讲解:

文件下载,比较麻烦的就是不同浏览器文件名中文处理,因此,在代码中,需要针对不同的浏览器做处理。这里:火狐的 是文件名中文需要 base64 编码,而 ie/chrome 是 URL编码。针对不同的浏览器,我们需要进行不同的编码处理。

在这里插入图片描述

// 4. 给http响应,设置响应头Content-Dispostion
        // 这里考虑的细节比较多,比如不同的浏览器写法不一样,考虑编码
        // ff 是文件名中文需要 base64, 而 ie/chrome 是 URL编码
        // 这里我们不需要同学门机制,只需知道原理
        if(request.getHeader("User-Agent").contains("Firefox")) {
            // 火狐浏览器的设置 为 Base64编码
            response.setHeader("Content-Disposition","attachment; filename==?UTF-8?B?" +
                    new BASE64Encoder().encode(downLoadFileName.getBytes("UTF-8")));
        } else {
            // 其他(主流ie/chrome) 使用 URL编码操作
            response.setHeader("Content-Disposition","attachment; filename=" +
                    URLEncoder.encode(downLoadFileName,"UTF-8"));
        }

运行测试:

在这里插入图片描述

在这里插入图片描述

3.2 文件下载注意事项和细节

  1. 文件下载,比较麻烦的就是不同浏览器文件名中文处理,因此,在代码中,需要针对不同的浏览器做处理。在这里插入图片描述

  2. 对于网站的文件,很多文件使用另存为即可下载,对于大文件(文档,视频),会使用专 业的下载工具(迅雷、百度,腾讯,华为网盘等)

  3. 对于不同的浏览器, 在把文件下载完毕后,处理的方式不一样, 有些是直接打开文件,有些是将文件下载到 本地/下载目录。

4. 总结:

  1. 文件上传的表单上的属性上的处理:method 指定为 post ,因为文件上传是比较大的文件, get 无法发送较大的文件。enctype:encodetype 编码类型,要设置为:multipart/form-data ,表示进行二进制文件的提交,multipart/form-data: 表示表单提交的数据是有多个部分组成,也就是可以提交二进制数据和文本数据,两者都行。

  2. 有时-》上传失败了,可能是目录的问题 ,加上 “/”

  3. 为了防止大量的目录创建,可以增加日期时间进行创建多个目录,这样以日期天数进行创建目录的话,一年最多也就是 365个目录而已。

  4. 文本被替换覆盖的问题,我们也一个工具类,让文件名不重复

    // UUID.randomUUID().toString() 哈希不重复值
    // System.currentTimeMillis() 获取当当前系统时间毫秒级别的
    // 对上传的文件名进行处理,前面增加一个前缀,保证是唯一即可
    // 同时使用特定的 "_" 符号进行分割,用于后续可能需要拿到文件名,最方便使用

    name = UUID.randomUUID().toString() + "_" + System.currentTimeMillis() + "_" + name;
    String fileFullPath = fileRealPathDirectory + "/" + name;
    
    
  5. 文件上传功能,在项目中建议有限制的使用,一般用在头像、证明、合同、产品展示等, 如果不加限制,会造成服务器空间被大量占用 [比如 b 站评论,就不能传图片,微信发 1 次朋友圈最多 9 张图等..]

  6. 文件下载:一个小细节:如果 web目录下创建的 目录是一个空文件夹/空目录,就是目录下没有东西的话,就算重启了 Tomcat 服务器也是不会添加到 out 目录下的。所以,只 需在 upload 目录下,放一个文件即可, 这个是 Idea + Tomcat 的问题, 实际开发不会存在。

  7. 文件下载,比较麻烦的就是不同浏览器文件名中文处理,因此,在代码中,需要针对不同的浏览器做处理。这里:火狐的 是文件名中文需要 base64 编码,而 ie/chrome 是 URL编码。针对不同的浏览器,我们需要进行不同的编码处理。

在这里插入图片描述

  1. 关于文件上传和下载,这里使用的是原生API的方式,在实际的开发中,我们这些关于文件上传和下载,都是被框架封装好了的,比如 :Spring MVC,Spring Boot 等等,我们只需要调用对应的API即可,框架封装的太好了,我们很难了解其中的底层原理。这里的文件上传和下载就是其底层原理了。

5. 最后:

限于自身水平,其中存在的错误,希望大家给予指教,韩信点兵——多多益善,谢谢大家,后会有期,江湖再见 !!!

在这里插入图片描述

From:https://www.cnblogs.com/TheMagicalRainbowSea/p/18327265
本文地址: http://www.shuzixingkong.net/article/489
0评论
提交 加载更多评论
其他文章 Golang 高性能 Websocket 库 gws 使用与设计(一)
GitHub 🌟 1.2k,高性能的 websocket 库,代码双语注释,适合有开发经验的同学进阶学习。
Golang 高性能 Websocket 库 gws 使用与设计(一) Golang 高性能 Websocket 库 gws 使用与设计(一) Golang 高性能 Websocket 库 gws 使用与设计(一)
useRoute 函数的详细介绍与使用示例
title: useRoute 函数的详细介绍与使用示例 date: 2024/7/27 updated: 2024/7/27 author: cmdragon excerpt: 摘要:本文介绍了Nuxt.js中useRoute函数的详细用途与示例,展示了如何在组合式API中使用useRoute获取
useRoute 函数的详细介绍与使用示例 useRoute 函数的详细介绍与使用示例
【AppStore】IOS应用上架Appstore的一些小坑
前言 上一篇文章写到如何上架IOS应用到Appstore,其中漏掉了些许期间遇到的小坑,现在补上 审核不通过原因 5.1.1 Guideline 5.1.1 - Legal - Privacy - Data Collection and Storage 5.1.1(ii) Permission Ap
【AppStore】IOS应用上架Appstore的一些小坑 【AppStore】IOS应用上架Appstore的一些小坑 【AppStore】IOS应用上架Appstore的一些小坑
带你学习通过GitHub Actions如何快速构建和部署你自己的项目,打造一条属于自己的流水线
本文主要讲解通过github的actions来对我们项目进行ci/cd System.out.println(&quot;原文地址:https://www.cnblogs.com/ancold/p/18327097&quot;); 一、actions简介 GitHub Actions 是一种持续集成
带你学习通过GitHub Actions如何快速构建和部署你自己的项目,打造一条属于自己的流水线 带你学习通过GitHub Actions如何快速构建和部署你自己的项目,打造一条属于自己的流水线 带你学习通过GitHub Actions如何快速构建和部署你自己的项目,打造一条属于自己的流水线
Android低功耗子系统的投票机制以及触发进入系统休眠的过程
从kernel角度看,系统是否进入休眠应该由内核来控制,因此Linux引入了 wakeup source以及autosleep机制 关于wakeup source的介绍,请参考: Wakeup Source框架设计与实现 关于autosleep机制,请参考:autosleep框架设计与实现 在内核中
Android低功耗子系统的投票机制以及触发进入系统休眠的过程 Android低功耗子系统的投票机制以及触发进入系统休眠的过程 Android低功耗子系统的投票机制以及触发进入系统休眠的过程
自写ApiTools工具,功能参考Postman和ApiPost
近日在使用ApiPost的时候,发现新版本8和7不兼容,也就是说8不支持离线操作,而7可以。 我想说,我就是因为不想登录使用才从Postman换到ApiPost的。 众所周知,postman时国外软件,登录经常性抽风,离线支持也不太好。 所以使用apipost,开始用apipost7一直很好用。可是
自写ApiTools工具,功能参考Postman和ApiPost 自写ApiTools工具,功能参考Postman和ApiPost
Git的存储原理
目录Git 设计原理Git vs SVNGit 存储模型.git 目录结构Git 基本数据对象Git 包文件Git 引用 Git 设计原理 概括的讲,Git 就是一个基于快照的内容寻址文件系统。 往下慢慢看。 Git vs SVN Git 出现前,主流版本控制系统(SVN...)一般为基于增量(de
Git的存储原理 Git的存储原理 Git的存储原理
前缀和与差分
前缀和与差分 前缀和 定义:前缀和可以简单理解为「数列的前 n 项的和」,是一种重要的预处理方式,能大大降低查询的时间复杂度。 一维前缀和 模板 for (int i = 1; i &lt;= n; i++) sum[i] = sum[i - 1] + a[i]; 时间复杂度:O(n) 原理 数组s
前缀和与差分 前缀和与差分