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" id="prevView"> <input type="file" name="pic" id=""
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. 最后:

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

Java SE 文件上传和文件下载的底层原理的更多相关文章

  1. Java Web文件上传

    参考资料:http://www.cnblogs.com/xdp-gacl/p/4200090.html 一.问题描述 Java Web文件上传需要借助一些第三方库,常用的是借助Apache的包,有两个 ...

  2. struts2的文件上传和文件下载

    实现使用Struts2文件上传和文件下载: 注意点: (1)对应表单的file1和私有成员变量的名称必须一致 <input type="file" name="fi ...

  3. CentOS下安装配置NFS并通过Java进行文件上传下载

    1:安装NFS (1)安装 yum install nfs-utils rpcbind (2)启动rpcbind服务 systemctl restart rpcbind.service 查看服务状态 ...

  4. 分享知识-快乐自己:SpringMvc中的单多文件上传及文件下载

    摘要:SpringMvc中的单多文件上传及文件下载:(以下是核心代码(拿过去直接能用)不谢) <!--设置文件上传需要的jar--> <dependency> <grou ...

  5. java进行文件上传,带进度条

    网上看到别人发过的一个java上传的代码,自己写了个完整的,附带源码 项目环境:jkd7.tomcat7. jar包:commons-fileupload-1.2.1.jar.commons-io-1 ...

  6. java实现文件上传下载

    喜欢的朋友可以关注下,粉丝也缺. 今天发现已经有很久没有给大家分享一篇技术文章了,于是想了一下给大家分享一篇java实现文件上传下载功能的文章,不喜欢的希望大家勿喷. 想必大家都知道文件的上传前端页面 ...

  7. Java实现文件上传到服务器(FTP方式)

    Java实现文件上传到服务器(FTP方式) 1,jar包:commons-net-3.3.jar 2,实现代码: //FTP传输到数据库服务器 private boolean uploadServer ...

  8. Java Web文件上传原理分析(不借助开源fileupload上传jar包)

    Java Web文件上传原理分析(不借助开源fileupload上传jar包) 博客分类: Java Web   最近在面试IBM时,面试官突然问到:如果让你自己实现一个文件上传,你的代码要如何写,不 ...

  9. Java超大文件上传解决办法

    这里只写后端的代码,基本的思想就是,前端将文件分片,然后每次访问上传接口的时候,向后端传入参数:当前为第几块文件,和分片总数 下面直接贴代码吧,一些难懂的我大部分都加上注释了: 上传文件实体类: 看得 ...

  10. java+大文件上传解决方案

    众所皆知,web上传大文件,一直是一个痛.上传文件大小限制,页面响应时间超时.这些都是web开发所必须直面的. 本文给出的解决方案是:前端实现数据流分片长传,后面接收完毕后合并文件的思路. 实现文件夹 ...

随机推荐

  1. HiveSQL 工作实战总结

    记录一些工作中有意思的统计指标,做过一些简化方便大家阅读,记录如有错误,欢迎在评论区提问讨论~ 问题类型 连续问题 两种思路 第一种:日期减去一列数字得出日期相同,主要是通过row_number窗口函 ...

  2. LeetCode 409. Longest Palindrome 最长回文串(C++/Java)

    题目: Given a string which consists of lowercase or uppercase letters, find the length of the longest ...

  3. tomcat部署Jenkins

    安装环境 jdk 1.8 tomcat 9.0 jenkins 2.290 准备工作 安装好Tomcat,8080端口启动 安装好jdk,配置好环境变量 ECS服务器安全组放开8080端口 关闭防火墙 ...

  4. Vue学习:22.Vue组件库-Vant

    Vue组件库是一系列预先构建好的.可复用的UI组件集合,它们设计用于加速Vue.js应用程序的开发过程.这些组件通常遵循一定的设计规范,提供统一的外观和交互体验,让开发者能够快速搭建用户界面. 组件库 ...

  5. MyBatis 的在使用上的注意事项及其辨析

    1. MyBatis 的在使用上的注意事项及其辨析 @ 目录 1. MyBatis 的在使用上的注意事项及其辨析 2. 准备工作 3. #{ } 与 ${ } 的区别和使用 {} 3.1 什么情况下必 ...

  6. Css var 的基础使用

    Css var 语法 var(custom-property-name, value) - custom-property-name 必须 变量必须以 --开头 后面可以是英文.数字连接符,区分大小写 ...

  7. 从 Modbus 到 Web 数据可视化之 WebSocket 实时消息

    前言 工业物联网是一个范围很大的概念,本文从数据可视化的角度介绍了一个最小化的工业物联网平台,从 Modbus 数据采集到前端数据可视化呈现的基本实现思路.这里面主要涉及基于 Modbus 通讯规约的 ...

  8. FFmpeg GL-transition转场的简单使用体验

    写在前面 最近在处理视频,遇到两个视频之间的转场用原生的 xfade写起来很痛苦,实现成本高,难度大:我这里主要用的FFmpeg,就想找一个插件专门干转场这个事:搜索了一翻后找到 GL-transit ...

  9. typroa破解

    Typora 一款 Markdown 编辑器和阅读器 风格极简 / 多种主题 / 支持 macOS,Windows 及 Linux 实时预览 / 图片与文字 / 代码块 / 数学公式 / 图表 目录大 ...

  10. 海思SDK 学习 :002-实例代码分析

    背景 需要了解 海思HI35xx平台软件开发快速入门之背景知识,为了方便测试,还需要了解 海思SDK 的安装 知识 由于海思的应用程序启动 MPP 业务前,必须完成 MPP 系统初始化工作.同理,应用 ...