前言
最近一段时间做过很多关于文件及音视频相关的工作,故此根据个人实践结合资料查询学习,总结了关于文件、二进制相关的内容和大家分享交流,如果文章有不对的地方,恳请指正,✅通过本文,你将了解或者加深对以下概念的认知以及其应用场景:
- File
- Blob
- ArrayBuffer
- TypeArray
- DataView
- Buffer
- FileReader
- URL.createObjectURL
以及如何实现 ?
- 如何预览图片?
- 如何截取视频首帧(如果不借助第三方oss能力呢)?
1. Blob
Blob是对大数据块的不透明引用或者句柄。名字源于SQL数据库,表示"二进制大数据"(Binary Large Object)。在JavaScript中Blob通常表示二进制数据,但是不一定是大量数据。Blob是不透明的,我们可以对它执行的操作只有获取它的大小,MIME类型和将他切割成更小的Blob。------
《JavaScript权威指南》
1.1 主要用法
1.1.1 构造函数使用
第一个参数是一个包含实际数据的数组(二进制数组也行
),第二个参数是数据的类型(MIME type)
1.1.2 现有实例使用
其构造器Blob原型有size和type属性分别表示blob包含数据的大小和数据类型,其原型上方法有
text
(返回promise)和slice
, 其中slice(start, end),返回start和end为切割blob的开始和结束字节点,返回值为blob
1.2 应用场景
- 大文件上传(阿里云)时,会对file继承的blob的slice属性,对文件进行切割分片上传
- 可以通过blob结合FileReader来把其换成其他需要的数据比如
Arraybuffer
- 可以暂存arrayBuffer数据,并通过URL.createObjectUrl(blob)通过同步的方式将其转成url以提供引用
2. File
File继承Blob,并基于用户的操作系统拓展了blob,使用户可以通过浏览器安全的访问系统的
3. 获取File
3.1 从操作系统获取
3.1.1 input元素返回的filelist
3.1.2 拖放操作获取File
3.2 从网络中获取
从网络中获取文件(资源图片、视频),常用方式有两种
- 资源是存在cdn上面,通过url去访问获取资源
- 通过推流的方式获取资源(图片)
方式 | 特点 | 应用场景 |
---|---|---|
cdn | 不经常变动的资源 | 前端的脚本、用户信息头像、商品图、素材库图等存储资源 |
二进制流 | 经常变动、实时生成 | 二维码(小程序体验版),ai画像等 |
获取的二进制流可以通过blob转成url或者通过bota编码转成base64(见下文)
4. 操作文件
4.1 上传大文件对文件(file/blob)进行分割
4.2 图片裁切 🔧中将blob同步转为base64(dataul)
当然也可以通过FileReader异步的读取blob/file来获取base64
4.3 Base64 转为 blob
4.4 处理音视频首帧
上述流程简单说明:
- 通过拖拽或者选择的方式选取我们要上传的视频
file
- (
为了获取视频首帧
)通过createObjectUrl(file)获取到视频url - 通过videoElement引用url,并插入dom
- 通过loadeddata事件回调🪝,canvas创建画布,通过toDataURL获取到视频首帧截图
- 然后再将其转成blob(
4.3 Base64 转为 blob
) - 调用oss方法上传首帧文件
5. ArrayBuffer/ DataView / TypeArray
为什么要使用它
- 原生的Array内部为了实现灵活性,牺牲了一部分性能,采用了哈希表的数据结构(详情)
- buffer通过分配连续的内存,访问较原生array哈希方式遍历链表索引 更加高效
5.1 实例
canvas typeArray
Array vs Buffer
名称 | 特点 | 应用场景 |
---|---|---|
Array | 不限制类型、可能是稀疏的、不定长度 | 大部分场景及json |
ArrayBuffer | 实际存储数据的地方(内存) | XHR、File API、Canvas等等各种地方,读取了一大串字节流 |
TypedArray | 单个长度和类型固定、字节对齐 | 音视频、canvas、录音,处理网络中接收到的二进制数据,webwork中的大量数据处理 |
DataView | 每个元素长度和类型不固定,更灵活,性能相对较差 | 构建二进制文件或格式话文件 |
js
5.2 ArrayBuffer
以上述代码为例子:
- line1: 申请了一个大小为8byte的内存大小区域,其地址是arrBuff
- line2、line3: 在arrBuff对象上添加0 和a属性,并且为其赋值
注意:ArrayBuffer创建后,不能通过其引用直接操作(读写)它的元素,给其赋值,其实是在给其添加静态属性值,下面我们会证明为什么
这里后端拿到的response.data是arraybuffer并不能直接操作,而是通过typeArray进行操作的
5.3 TypeArray
注意!全局并没有TypeArray,其是作为
Int8Array
、Uint16Array
等子类的统称,只是这些之类的原型,,所以只能通过 Object.getPrototypeOf(Int8Array) 及类似方式访问
5.3.1 举个🌰子
图片标记点按照数字依次为:
- 二进制类型数组中的元素,默认填充0
- 类型数组实际引用的buffer,只读
- 每个字节的地址
- 不同类型type array
- 进制类型:dec(Decimal System)
十进制
,hex十六进制
, oct八进制
- 不同进制下对应的buffer存的数值
5.3.2 无符号类二进制数组
其形式为Uint +8^n +Array ,这里拿Uint8Array
举例: 对于无符号8比特(1byte)的类型的
由上图得出Uint8Array
有以下几点特点:
- 单个元素的可存值范围
[0, 256)
- 正向溢出,一直
减去
256,直至得到的值在[0, 256)
区间的数字,作为其最终值 - 负向溢出,一直
加
256,直至得到的值在[0, 256)
区间的数字,作为其最终值 - 溢出的值,不会加到下一个元素
5.3.3 有符号类二进制数组
上述代码稍作改动,有符号8比特(1byte)的
Int8Array
类型的
由上图得出Int8Array
有以下几点特点:
- 单个元素的可存值范围
[-128, 127)
; - 正向溢出,一直
减去
256,直至得到的值在[-128, 127)
区间的数字,作为其最终值 - 负向溢出,一直
加
256,直至得到的值在[-128, 127)
区间的数字,作为其最终值 - 溢出的值,不会加到下一个元素
5.3.4 主要用法及应用场景
- 使用arrayBuffer实例作为参数,提供操作arrayBuffer方法
- 创建一个特定类型的typearray
js
new TypedArray()
new TypedArray(length)
new TypedArray(typedArray)
new TypedArray(object)
new TypedArray(buffer)
new TypedArray(buffer, byteOffset)
new TypedArray(buffer, byteOffset, length)
同一类型,如
Int8Array
、Uint8Array
分为有符号和无符号,取值范围不同,其中取值范围表示,每个类数组(集合)的单个元素的取值范围
关于typeArray属性:
- length:上述代码构造函数中的3、6表示创建类型化数组长度(length)是3、6,即包含3、6个元素,用0填充
- bytelength, 数组的长度大小(字节),其为 BYTES_PER_ELEMENT * length
- BYTES_PER_ELEMENT: 类型化数组的每个元素的空间大小(字节)
注意在构建typeArray实例时
- 要注意传入的arrayBuffer的字节大小要和类型数组元素对齐,即buffer大小是自元素大小的整数倍,这样才能被类型元素平均分配
- 同理,偏移量offeset(字节),也要是BYTES_PER_ELEMENT倍数
关于字节对齐
: 下面是vue-cropper图片菜切开源项目,当传入到Uint16Array的字节大小不为2的整数倍时就会报错如图所示错误,所以需要对传入buffer的len做偶数适配
5.4 DataView
DataView
视图是一个可以从二进制ArrayBuffer
对象中读写多种数值类型的底层接口 ---Mdn
其中关于setInt8^n
和对应的getInt8^
,拿 view.setInt16(7, 120)
和 view.getInt16(7)
举例🌰, 调用过程是这样的:
- 通过
var buff10 = new ArrayBuffer(10)
偷到了ArrayBuffer老巢的地址,于是上门去找ArrayBuffer
了 - 到了之后,从前面偏移(空出来)7个字节开始,隔出来一个2byte的内存空间
专门
来存放有符号数据120
view.getInt16(7)
从前面偏移(空出来)7个字节开始,获取一个2byte的内存空间专门
内存的值
上述代码,内存操作示意图:
通过上述代码,我们可知:
- TypeArray和DataView都可以操作ArrayBuffer,但是前者更加灵活,每个元素长度和类型都可以不一致
- DataView 不会有对齐问题
##6. 总结
使用类型化数组的关键是性能和内存。它们最常用于特殊场景,但是当您只需要存储数值(或 utf-8 字符串、加密向量等)时,在普通情况下使用它们并没有什么问题。它们速度快且内存占用少
参考
- Arraybuffer、Blob、File、Buffer详解、作用以及相互转化
- 我妈都看得懂的 Buffer 基础
- Where to use ArrayBuffer vs typed array in JavaScript?
- javascript ArrayBuffer, what's it for?
- 怎么理解 JavaScript 中的 ArrayBuffer?
- 前端内存优化的探索与实践
- V8 ---
DataView
vs.TypedArray
peak performance - 阮一峰ArrayBuffer
- 二进制的原码、反码、补码
- What are Blobs used for in JavaScript?