当前位置: 首页 > news >正文

文件上传都发生了啥

一直在用组件库做文件上传,那里面的原理到底是啥,自己写能不能写一个upload框出来呢?

(一)基本原理

浏览器端提供了一个表单,在用户提交请求后,将文件数据和其他表单信息编码并上传至服务器端,服务器端将上传的内容进行解码了,提取出 HTML 表单中的信息,将文件数据存入磁盘或数据库

就是编码-解码-放数据库其实。

浏览器采用默认的编码方式是 application/x-www-form-urlencoded , 可以通过指定 form 标签中的 enctype 属性使浏览器知道此表单是用 multipart/form-data 方式编码如:

<form enctype="multipart/form-data" 
action="${pageContext.request.contextPath}/servlet/uploadServlet2" method="post" > 

(二)详解

2.1编码篇

我们在上传文件的时候用的编码都是form-data的形式,不上传文件的时候都是用application/x...的格式,这是为啥呢?

总的来说,application传输二进制文件或者非ASCII码字符的时候会对数据进行大量转义,导致传输效率低下,而且也不支持多文件传输。
相对的,form-data的数据处理形式就比较适合传输文件,他会把表单数据分成多个部分,每个部分有自己的头部信息和数据内容,而且还用分隔符分割,数据内容可以是文本或二进制数据,可以是文件或表单字段的值。他还支持多文件多表单数据的传输,所以经常用他来传输文件。

但大文件上传就不用这个了,用的是application/octet-stream,后面再说。

2.1.1 常见的application/x-www-form-urlencoded

如果你打开network就知道很多表单提交请求都是用的application/x-www-form-urlencoded这个编码方式,编码结果就是name=xiaoming&age=18

在这种编码格式中,所有字段名和值都被URL编码,并使用等号(=)分隔。每个字段之间使用与号(&)分隔。但让他来处理数据庞大的二进制数据或者包含非 ASCII 字符的数据的时候就会显得力不从心。因为它是一种文本格式,只能处理 ASCII 字符集中的字符,无法处理二进制数据。在该编码格式中,所有的非 ASCII 字符都需要进行编码,这会导致二进制数据被大量转义,使得数据量变得非常大,传输效率低下。另外,该编码格式也不支持多部分传输,无法将多个文件或多个表单字段一起传输。因此,如果需要传输二进制文件或多个表单字段,应该使用其他编码格式,如 multipart/form-data。该编码格式支持二进制数据和多部分传输,可以更有效地传输文件和表单数据。

2.1.2 multipart/form-data

使用这个编码的时候,会把表单数据分成多个部分,每个部分都有自己的头部信息和数据内容。各部分之间用一个不可能出现的分隔符分隔,分隔符由一个随机字符串和两个连字符组成,例如:–boundary。每部分都对应表单中的一个input

这种编码方式先定义好 一个不可能在数据中出现的字符串作为分界符,然后用它将各个数据段 分开,而对于每个数据段都对应着 HTML 页面表单中的一个 Input 区,包括一个 content-disposition 属性,说明了这个数据段的一些信息,如果这个数据段的内容是一个文件,还会有 Content-Type 属性,然后就是数据本身。

头部信息有这个部分的类型、名称和其他元数据。对于文件上传还包含了文件名和文件类型等信息。数据内容可以是文件或者表单的值。

multipart/form-data 是一种常见的编码格式,用于在 HTTP 协议中传输二进制数据和多部分数据。它通常用于上传文件或提交包含多个表单字段的表单数据。

在 multipart/form-data 编码中,数据被分成多个部分,每个部分都有自己的头部信息和数据内容。每个部分之间用一个特殊的分隔符分隔,分隔符由一个随机字符串和两个连字符组成,例如:–boundary。

每个部分的头部信息包含了该部分的类型、名称和其他元数据。对于文件上传,头部信息还包含了文件名和文件类型等信息。数据内容可以是文本或二进制数据,可以是文件或表单字段的值。

multipart/form-data 编码格式支持上传多个文件和多个表单字段,可以同时上传多个文件和表单字段。它还支持断点续传和上传进度显示等功能,可以更好地满足文件上传的需求。

需要注意的是,multipart/form-data 编码格式会增加数据传输的开销,因为每个部分都需要添加头部信息和分隔符。因此,在传输大文件或大量数据时,应该考虑使用其他编码格式,如 application/octet-stream。

2.1.3 思考

也许你有疑问?那可以用 application/json 吗?

其实我认为,无论你用什么都可以传,只不过会要综合考虑一些因素的话,multipart/form-data 更好。例如我们知道了文件是以二进制的形式存在,application/json 是以文本形式进行传输,那么某种意义上我们确实可以将文件转成例如文本形式的 Base64 形式。但是呢,你转成这样的形式,后端也需要按照你这样传输的形式,做特殊的解析。并且文本在传输过程中是相比二进制效率低的,那么对于我们动辄几十 M 几百 M 的文件来说是速度是更慢的。

以上为什么文件传输要用 multipart/form-data 我还可以举个例子,例如你在中国,你想要去美洲,我们的 multipart/form-data 相当于是选择飞机,而 application/json 相当于高铁,但是呢?中国和美洲之间没有高铁啊,你执意要坐高铁去,你可以花昂贵的代价(后端额外解析你的文本)造高铁去美洲,但是你有更加廉价的方式坐飞机(使用 multipart/form-data)去美洲(去传输文件)。你图啥?

为什么要编码?

  1. 传递过程中要进行编码来制定发送的文件数据规则,以便于后端能够实现一套对应的解析规则
  2. 传递的数据规则里包含所传递文件的基本信息 ,如文件名与文件类型,以便后端写出正确格式的文件

2.1.4 实践一下

如果一不小心真用这个来传文件了会怎么样?

首先我们先写下最简单的一个表单提交方式。

<form action="http://localhost:7787/files" method="POST"><input name="file" type="file" id="file"><input type="submit" value="提交">
</form> 

我们选择文件后上传,发现后端返回了文件不存在。

image.png

不用着急,熟悉的同学可能立马知道是啥原因了。

我们打开控制台,由于表单提交会进行网页跳转,因此我们勾选 preserve log 来进行日志追踪。

image.png

我们可以发现其实 FormData 中 file 字段显示的是文件名,并没有将真正的内容进行传输。再看请求头。

image.png
发现是请求头和预期不符,也印证了 application/x-www-form-urlencoded 无法进行文件上传。

我们加上请求头,再次请求。

<form action="http://localhost:7787/files" enctype="multipart/form-data" method="POST"><input name="file" type="file" id="file"><input type="submit" value="提交">
</form> 

image.png

发现文件上传成功,简单的表单上传就是像以上一样简单。但是你得熟记文件上传的格式以及类型。

2.2 提交的时候

在文件上载和表单提交的过程中,有两个指的关心的问题,一是 上载的数据是是采用的那种方式的编码,这个问题的可以从 Content-Type 中得到答案,另一个是问题是上载的数据量有多少Content-Length , 知道了它,就知道了 HttpServletRequest 的实例(java)中有多少数据可以读取出来。

2.2.1 后端不解析的话会拿到啥?

在开头的时候就说了整个文件上传的流程就是表单提交-编码-解析-保存,

下面的内容示范了后端不去处理上传的数据内容时会受到什么样的数据。

ps:form 表单提交操作网页会造成整体刷新,所以一般比较少用,而是利用熟悉的异步请求操作 AJAX 来完成上传动作,而一个新的问题出现了,不使用 form 表单,那文件编码该怎么处理呢?

答案是自己生成 FormData 的实例咯,

const form = new FormData()

然后用 form.append('file': fileData)来放数据

<form method="POST" enctype="multipart/form-data"> <input type="file" name="file" value="请选择文件"><br /><input type="submit">
</form>

下面提供NodeJs示例:

//上传接口逻辑
if (url === "/upload" && method === "POST") {// 定义一个缓存区const arr = [];req.on("data", (buffer) => {// 将前端传来的数据进行存储进缓存区arr.push(buffer);});req.on("end", () => {// 前端请求结束后进行数据解析 处理const buffer = Buffer.concat(arr);// 将数据变成string类型const content = buffer.toString();// 从传来的数存进test的文件里fileStream("test").write(buffer);// 返回前端请求完成res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });res.end("上传完成");});
}

这里的服务端代码先将前端上传的数据内容毫不处理直接写入一个名为 test 的文件内,以便我们查看前端到底传来了什么样的数据。

上传上面的html文件,这是前端的部分请求头。

image.png

Conten-Type中,前面的是数据类型,后面就是分隔符啦。

这是后端得到的数据(请求体)

------WebKitFormBoundary7YGEQ1Wf4VuKd0cE
Content-Disposition: form-data; name="file"; filename="index.html"
Content-Type: text/html// 这里是文件内容
<html><head><title>上传文件</title></head><body><form method="POST" enctype="multipart/form-data"><input type="file" name="file" value="请选择文件"><br /><input type="submit"></form></body>
</html>
------WebKitFormBoundary7YGEQ1Wf4VuKd0cE--

2.2.2 后端是如何解析的

从上面的示例可以看到,后端拿到的请求体其实跟我上传拿到文件内容差不多,但多了一些东西,所以后端解析器的目的就是去掉这几行内容,并且在这几行简要信息里摘出文件名,以便写文件。

先是第一行和最后一行的 WebKitFormBoundary 码,第二行的 ContentDisposition,该行包含一些文件名等基本信息,还有第三行文件内容类型,所以后端如果要获取到正确的文件内容则需要自己去除(直接用 indexof + length + substr 就可以)由浏览器在上传时所添加的进来的几行内容,而保留有效文件内容后进行写文件操作,完成上传目的

自己写一个解析器:拆成数组,然后字符串操作进行删和拿。

/*** @step1 过滤第一行* @step2 过滤最后一行* @step3 过滤最先出现Content-Disposition的一行* @step4 过滤最先出现Content-Type:的一行*/
const decodeContent = content => {let lines = content.split('\n');const findFlagNo = (arr, flag) => arr.findIndex(o => o.includes(flag));// 查找 ----- Content-Disposition Content-Type 位置并且删除const startNo = findFlagNo(lines, '------');lines.splice(startNo, 1);const ContentDispositionNo = findFlagNo(lines, 'Content-Disposition');lines.splice(ContentDispositionNo, 1);const ContentTypeNo = findFlagNo(lines, 'Content-Type');lines.splice(ContentTypeNo, 1);// 最后的 ----- 要在数组末往前找const endNo = lines.length - findFlagNo(lines.reverse(), '------') - 1;// 先反转回来lines.reverse().splice(endNo, 1);return Buffer.from(lines.join('\n'));}

一个简单的解析器完成了,一般情况下你所使用的框架会解决解码这一部分问题,无论是 Nodejs 或是 Java,他们的本质都是摘出有效的文件内容然后写进新文件里,从而达到文件上传的目的。

最终的服务器代码如下:

// 最终的服务端代码
if(url ==='/upload' && method === 'POST') {
//文件类型
const arr = []
req.on('data', (buffer) => {arr.push(buffer);
})
req.on('end', () => {const buffer = Buffer.concat(arr);const content = buffer.toString();const result = decodeContent(content);const fileName = content.match(/(?<=filename=").*?(?=")/)[0];fileStream(fileName).write(result);res.writeHead(200, {  'Content-Type': 'text/html; charset=utf-8' });res.end('上传完成')
})
}

2.2.3 不用formdata,直接用file对象呢

上面那些编码的意义都是规范,都是为了前后端更好地进行协作开发。

当我们遇到了无form表单的情况时,该怎么上传文件。

文件上传的实质是上传文件的内容以及文件的格式,当我们使用 HTML 提供的 Input 上传文件的时候,它将文件的内容读进内存里,那我们直接将内存里的数据当成普通的数据提交到服务端可以么?

image.png

上面的代码中,直接用input框的file对象写了。

然后直接跟之前提到的后端代码一样,什么解析都不做,直接写到txt文件里,打开你就会发现这就是自己上传的文件,一模一样,后台解析都不用解析。

简单介绍一下file对象

用上面的代码尝试一下。

image.png

没上传任何内容打印了一下 file 变量,是 undefined,然后我上传了一张图片,再次打印后 file 变量是一个 File 函数构造出的对象了,它里面有文件的一些简略信息,如大小 size,文件名 name 以及文件格式 type 等,而且文件内容也在这个对象里,只不过以 ArrayBuffer 的方式在文件的原型链上体现,看看下面对于 File 对象的操作。

image.png

上面这些数字其实就是文件的内容,大家都知道数据是 0,1 组成的世界,而 ArrayBuffer 则是更多的数字来体现的数据世界,它和二进制的目的是一样的,它被用来表示通用的、固定长度的原始二进制数据缓冲区。说到这里则必须要提起一个新的概念,浏览器的提供的 Blob 接口。

blob对象

Blob 对象表示一个不可变、原始数据的类文件对象。上面的 file 变量的构造函数 File 就是继承与基于 Blob,继承了 blob 的功能并将其扩展使其支持用户系统上的文件。看看下面的 Blob 与 File 的示例。

image.png

上面我先打印了一下 file 与浏览器提供的构造函数 File 和 Blob 的关系,然后自行构建了自定义的 myfile 对象和 myblob 的对象,看得出自行构建的 File 对象下会多出一些文件相关的属性,而 Blob 对象则只是基本的 size 与 type 属性。当打印 arrayBuffer 函数的返回值时发现其内容也是完全一致的。

其实说到这里很多人对于 Blob 是个啥还是一知半解的,简单理解一下,它的构造结果是一块内存区,这块内存区以特定的格式存储我们所要上传的文件二进制数据,当我们上传文件时上传这块内存区里的数据即可。

参考文章1
参考文章1

相关文章:

文件上传都发生了啥

一直在用组件库做文件上传&#xff0c;那里面的原理到底是啥&#xff0c;自己写能不能写一个upload框出来呢? &#xff08;一&#xff09;基本原理 浏览器端提供了一个表单&#xff0c;在用户提交请求后&#xff0c;将文件数据和其他表单信息编码并上传至服务器端&#xff0…...

【vim进阶】vim编辑器的多文件操作(如何打开多个文件,如何进行文件间的切换,如何关闭其中的某一个文件)

一、如何打开多个文件&#xff1f; 方法一&#xff1a;启动打开 现在有多个文件 file1 &#xff0c;file2 , … ,filen. 现在举例打开两个文件 file1&#xff0c;file2 vim file1 file2该方式打开文件&#xff0c;显示屏默认显示第一个文件也就是 file1。 方法二&#xff…...

ToBeWritten之车辆通信

也许每个人出生的时候都以为这世界都是为他一个人而存在的&#xff0c;当他发现自己错的时候&#xff0c;他便开始长大 少走了弯路&#xff0c;也就错过了风景&#xff0c;无论如何&#xff0c;感谢经历 转移发布平台通知&#xff1a;将不再在CSDN博客发布新文章&#xff0c;敬…...

自定义 Jackson 的 ObjectMapper, springboot多个模块共同引用,爽

springboot多个模块共同引用自定义ObjectMapper &#x1f683;统一配置示例自定义 Jackson 的 ObjectMapper更改时区为东八区, 优点是在多个模块中都可以使用同一种方式来进行配置&#xff0c;方便维护和修改 统一配置 假设有一个 Spring Boot 项目&#xff0c;包含多个模块&…...

【面试】Redis面试题

文章目录概述什么是Redis&#xff1f;Redis有哪些优缺点&#xff1f;使用redis有哪些好处&#xff1f;为什么要用 Redis / 为什么要用缓存为什么要用 Redis 而不用 map/guava 做缓存?Redis为什么这么快Redis的应用场景持久化什么是Redis持久化&#xff1f;Redis 的持久化机制是…...

前端后端交互系列之原生Ajax的使用

目录前言一&#xff0c;Ajax概述二&#xff0c;基础知识之Http协议2.1 请求报文2.2 响应报文2.3 如何查看通信报文三&#xff0c;Ajax简单案例3.1 Express框架创建服务端3.2 Ajax案例后台准备3.3 Ajax案例前台准备3.4 发送get请求3.5 发送带有参数的Ajax请求3.6 发送post请求3.…...

openGauss 5.0企业版主从部署,实战狂飙

&#x1f4e2;&#x1f4e2;&#x1f4e2;&#x1f4e3;&#x1f4e3;&#x1f4e3; 哈喽&#xff01;大家好&#xff0c;我是【IT邦德】&#xff0c;江湖人称jeames007&#xff0c;10余年DBA及大数据工作经验 一位上进心十足的【大数据领域博主】&#xff01;&#x1f61c;&am…...

Vue中props组件和slot标签的区别

在 Vue 中&#xff0c;props 和 slot 都是组件之间进行通信的机制&#xff0c;它们的作用和应用场景有一些区别&#xff1a; props 是一种组件的数据传递机制&#xff0c;通过在父组件中以属性的形式向子组件传递数据。子组件接收这些数据&#xff0c;并可以进行相应的处理和渲…...

基于Windows下VSCode搭建Vue开发环境

一、准备工作 VSCode编辑器安装&#xff1a;https://code.visualstudio.com/Node.js安装&#xff1a;https://blog.csdn.net/qq_40197828/article/details/78302124VSCode插件安装&#xff1a;Vetur和ESlint 二、更换淘宝镜像源 更换镜像源命令&#xff1a;npm install -g c…...

Android开发 Dialog对话框 DatePickerDialog

1. AlertDialog AlertDialog是弹出的提醒对话框&#xff0c;有提示&#xff0c;确认&#xff0c;选择等功能。 没有公开的构造方法&#xff0c;一般用AlertDialog.Builder来完成参数设置&#xff0c;最后调用create方法创建。 参数设置常用的方法&#xff1a; 代码&#xff…...

开心档开发入门网之C++ Web 编程

C Web 编程什么是 CGI&#xff1f;公共网关接口&#xff08;CGI&#xff09;&#xff0c;是一套标准&#xff0c;定义了信息是如何在 Web 服务器和客户端脚本之间进行交换的。CGI 规范目前是由 NCSA 维护的&#xff0c;NCSA 定义 CGI 如下&#xff1a;公共网关接口&#xff08;…...

C# 和 VB .NET 的纯 FFmpeg 包装器:CSFFmpeg Crack

用于 C# 和 VB .NET 的纯 FFmpeg 包装器buildbuildpassingpassing releasereleasev1.0.3.0v1.0.3.0用于 C# 和 VB .NET Framework&#xff08;WinForm 和 WPF&#xff09;和 .NET Core 的纯 FFmpeg 包装器。 截图 主要 Winform 示例有据可查的例子目录&#xff1a; 关于截图好处…...

python外篇(序列化和非序列化)

目录 概念阐述 pickle json msgpack 概念阐述 序列化是指将对象转化为可存储或可传输的数据格式&#xff0c;例如将 Python 对象转化为二进制、JSON 或 XML 等格式&#xff0c;以便于将其存储到文件中或在网络上传输。在Python中&#xff0c;可以使用pickle、json、msgpac…...

Linux总结(二)

基础IO 1.什么叫文件? 我们需要在操作系统的角度理解文件。 文件 = 文件内容 + 属性(所以即使是空文件,也会占空间,因为我们是需要保存文件属性的,属性也是数据,所以占空间) C/C++程序默认会打开三个文件流,叫做标准输入(stdin),标准输出(stdout),标准错误(std…...

【4.1】Socket编程、TCP挥手

TCP连接断开 四次挥手 四次挥手过程 客户端发送FIN报文&#xff0c;客户端进入FIN_WAIT_1状态。 服务端接收报文&#xff0c;发送ACK报文&#xff0c;服务端进入CLOSE_WAIT状态。 客户端收到ACK报文&#xff0c;进入FIN_WAIT_2状态。 服务端处理完数据后&#xff0c;也发送…...

【竞赛经历】CSDN第41期竞赛题解

1 前言 本次的竞赛主要是最后一题&#xff0c;对于完全不懂珠算的人来说还是有点困难的&#xff0c;仅理解题目的意思就花了很多时间&#xff0c;最后侥幸拿了第一个前三。。。 2 题解 本次竞赛分为编程题部分和非编程题部分&#xff0c;其中非编程题部分比较简单。 2.1 非编…...

【Linux学习】信号——预备知识 | 信号产生 | 核心转储

&#x1f431;作者&#xff1a;一只大喵咪1201 &#x1f431;专栏&#xff1a;《Linux学习》 &#x1f525;格言&#xff1a;你只管努力&#xff0c;剩下的交给时间&#xff01; 信号&#x1f514;信号&#x1f3b5;预备知识&#x1f3b5;信号处理方法的注册&#x1f514;信号…...

2023中国程序员薪酬报告出炉,你拖后腿了吗?

程序员薪资高已是公认的事实&#xff0c;但是具体高到什么程度呢&#xff1f;近期&#xff0c;全球人力服务公司 Michael Page Internatioal 就发布了《2023 中国大陆薪酬报告》&#xff0c;揭示了中国程序员的薪酬情况。 该报告中一共调研了国内 7 个行业以及 6 大城市不同职…...

Mac下Python3安装及基于Idea开发

本篇文章带大家基于Mac OS操作系统&#xff0c;下载、安装Python环境&#xff0c;并基于Idea编写第一个Demo。 Python3安装 访问Python官网&#xff1a;https://www.python.org/。找到“Download”菜单&#xff0c;点击下载&#xff1a; 此处下载的为Mac的安装包&#xff0c…...

2017年 团体程序设计天梯赛——题解集

前言&#xff1a; Hello各位童学大家好&#xff01;&#x1f60a;&#x1f60a;&#xff0c;茫茫题海你我相遇即是缘分呐&#xff0c;或许日复一日的刷题已经让你感到疲惫甚至厌倦了&#xff0c;但是我们真的真的已经达到了我们自身极限了吗&#xff1f;少一点自我感动&#xf…...

“唯一靶点”的华堂宁会成控糖爆品吗?

一上市&#xff0c;两次“断货”的货华堂宁有爆品那味儿了。 2022年10月28日华领医药-B&#xff08;02552.HK&#xff09;公告华堂宁&#xff08;多格列艾汀&#xff09;正式进入商业化&#xff0c;一周后各个渠道便进入到了断货和限售的状态。 对于一个不在传统九大降糖药品…...

Spring《三》DI依赖注入

&#x1f34e;道阻且长&#xff0c;行则将至。&#x1f353; 上一篇&#xff1a;Spring《二》bean的实例化与生命周期 下一篇&#xff1a;敬请期待 目录一、setter注入&#x1f349;1.注入引用数据类型2.注入简单数据类型二、构造器注入&#x1f34a;1.注入引用数据类型2.简单数…...

leetcode 面试题 17.06. 2出现的次数

编写一个方法&#xff0c;计算从 0 到 n (含 n) 中数字 2 出现的次数。 示例: 输入: 25 输出: 9 解释: (2, 12, 20, 21, 22, 23, 24, 25)(注意 22 应该算作两次) 该问题用的方法数数组dp&#xff0c;首先我通过总结规律写出了相关的code。使用一个dp数组记录10i10^i10i以内会出…...

CMake入门教程【基础篇】5.configure_file构建配置

configure_file配置文件 文章目录 知识点实例代码目录代码实现知识点 configure_fileconfigure_file 源文件转换为目标文件 实例 代码目录 |-📁prj4   |-- 🎴CMakeLists.txt   |-- 🎴TutorialConfig.h.in   |-- 🎴tutorial.cxx 代码实现 /prj4/CMakeLists.…...

软件开发可行性分析——健康食谱小程序

关于软件开发可行性分析先给大家介绍下面几个关键点&#xff1a; 什么是可行性分析&#xff1f; 检查并确定是否值得为项目或产品投入时间、金钱和资源。这样的评估活动称为“可行性分析”。 为什么要进行可行性分析&#xff1f; 在软件项目开发过程中&#xff0c;只要资源…...

ShuffleNet V1 对花数据集训练

目录 1. shufflenet 介绍 分组卷积 通道重排 2. ShuffleNet V1 网络 2.1 shufflenet 的结构 2.2 代码解释 2.3 shufflenet 代码 3. train 训练 4. Net performance on flower datasets 1. shufflenet 介绍 shufflenet的亮点&#xff1a;分组卷积 通道重排 mobilenet …...

测试人员转型是大势所趋:我的10年自动化测试经验分享

做测试十多年&#xff0c;有不少人问过我下面问题&#xff1a; 现在的手工测试真的不行了吗&#xff1f; 测试工程师&#xff0c;三年多快四年的经验&#xff0c;入门自动化测试需要多久&#xff1f; 自学自动化测试到底需要学哪些东西&#xff1f; 不得不说&#xff0c;随着行…...

Pandas高级操作,建议收藏(一)

在数据分析和数据建模的过程中需要对数据进行清洗和整理等工作&#xff0c;有时需要对数据增删字段。下面为大家介绍Pandas对数据的复杂查询、数据类型转换、数据排序的使用。 复杂查询 实际业务需求往往需要按照一定的条件甚至复杂的组合条件来查询数据,接下来为大家介绍如何…...

ASIC-WORLD Verilog(1)一日Verilog

写在前面 在自己准备写一些简单的verilog教程之前&#xff0c;参考了许多资料----asic-world网站的这套verilog教程即是其一。这套教程写得极好&#xff0c;奈何没有中文&#xff0c;在下只好斗胆翻译过来&#xff08;加了自己的理解&#xff09;分享给大家。 这是网站原文&…...

数据治理工具项目投标书技术部分-V1.6

本资料来源公开网络&#xff0c;仅供个人学习&#xff0c;请勿商用&#xff0c;如有侵权请联系删除 项目背景 二、项目目标 提供一套后勤数据治理工具部署文件及配套文档&#xff0c;主要技术指标如下&#xff1a; &#xff08;1&#xff09;具备数据抽取转换装载、元数据管理、…...