很多前端童鞋对二进制有一种天生的恐惧,觉得遥不可及,本文将带你从基础到实践,全面掌握日常可能面临的二进制操作。文中穿插的有些知识点,可以多琢磨一下,相信会对你有所帮助~
二进制数组
一种类似数组的对象,提供了一种用于访问原始二进制数据的机制。对于音频视频编辑,访问WebSockets的原始数据等,通过类型化数组来操作二进制数据将会非常有帮助。
二进制数组由三类对象组成:ArrayBuffer对象
、TypedArray视图
、DataView视图
。下面将仅对前两者做介绍。
ArrayBuffer对象
表示一个通用的、固定长度的二进制数据缓冲区,没有格式可言,并且不提供机制访问其内容。只能通过TypeArray视图读写。
//参数为开辟空间的字节长度
const buffer = new ArrayBuffer(32);
上面代码分配了一段连续的32字节的内存空间,每个字节的值默认为0。
支持的方法:
//buffer所占的字节长度
buffer.byteLength //32
//拷贝前两个字节返回一个新的ArrayBuffer对象
//先创建16字节长度的内存,再将前两个字节拷贝过去。
buffer.slice(0, 2)
TypedArray视图
视图提供了上下文,即数据类型、起始偏移量和元素数,将数据转换为实际有类型的数组,以指定格式解读二进制数据。
包含多种视图类型,通式为<type><位数>Array
:
Int8Array、Unit8Array、Int16Array、Uint16Array...
视图的区别
一个视图窗口占多少位,如Int16Array,表示视窗占2个字节。
如何建立视图
方式1: TypedArray(buffer, byteOffset=0, length?)
const buffer = new ArrayBuffer(12); // 12个字节
const view16 = new Int16Array(buffer, 2, 2) //每个视窗是2个字节
创建一个指向buffer的Int16视图,开始于字节2,长度为2。
注意,byteOffset必须是视窗所占字节数的整数倍,比如上例,只能是2,4。
方式2:TypedArray(length)
const view16 = new Int16Array(3);
直接分配6个字节(2*3)的内存空间。
方法3:TypedArray(typedArray)
const x = new Int8Array([1, 1]);
const y = new Int8Array(x);
x[0] // 1
y[0] // 1
x[0] = 2;
y[0] // 1
y会先创建一段新的内存(2个字节),再讲x的值拷贝到新内存空间中。
方法4:TypedArray(arrayLikeObject)
//可以接收一个数组作为参数
const view16 = new Int16Array([1, 2, 3]);
同样的会重新开辟内存,不会共享参数数组对应的内存。
对同一buffer操作,共享内存
TypedArray视图并不会存储数据,只是定义了读写规则,修改的是底层ArrayBuffer,看示例:
//开辟一段内存空间
buff = new ArrayBuffer(32);
//一次读取16位,即2个字节
x16 = new Int16Array(buff);
x16[0] = 1;
//使用Int8Array作为视窗操作
x8 = new Int8Array(buff);
x8[0] = 2;
x16[0] //2
可以看到不同视图对同一段内存操作,是内存共享的。
Symbol.iterator
const view8 = new Int8Array([1]);
打印下view8对象:
view8的原型链:Int8Array -> TypeArray -> Object
;
可以看到length属性
(注意区分length和byteLength)、Symbol.toStringTag
,同时还有Symbol.iterator
属性。这里介绍下遍历器接口。
我们知道一个对象部署了Symbol.iterator
(不清楚的可以看ES6精读【划重点系列】(一)),即表示可以被遍历,因此就可以使用for...of
:
//将类型数组转为普通数组
const normalArray = [...typedArray];
// or
const normalArray = Array.from(typedArray);
// or
const normalArray = Array.prototype.slice.call(typedArray);
支持的方法
普通数组的操作方法和属性,对 TypedArray 数组基本都适用。
1) 由TypedArray获取ArrayBuffer
const array = new Int16Array([1, 2, 3]);
array.buffer //TypedArray -> ArrayBuffer
2) 复制TypeArray
//在位移offset位置放置typedarray
typedarray.set(typedarray, offset);
3)length 和 byteLength
const a = new Int16Array(8);
a.length // 8
a.byteLength // 16
二进制读取顺序
先看下例中v16[0]是多少?
buffer = new ArrayBuffer(2);
v8 = new Int8Array(buffer);
v16 = new Int16Array(buffer);
v8[0] = 1;
v16[0] //??
有的同学可能会觉得下面这种:
//此时buffer中的值为:
0000 0001 0000 0000
//V8视窗是1个字节,那么其值为:
[0000 0001] [0000 0000] = > [1, 0]
//由于V16一个视窗2个字节,那么很简单
[0000 0001 0000 0000] => [256]
事实上,说明你陷入误区,机器和人是不一样的,它是从左往右读取的:地址空间中的高位字节存真正数据的高位,地址空间中的低位存数据低位,因此:
//此时buffer中的值为:
1000 0000 0000 0000
//V8视窗是1个字节,那么其值为:
[1000 0000] [0000 0000] = > [1, 0]
//由于V16一个视窗2个字节,那么很简单
[1000 0000 0000 0000] => [1]
应用
1) XMLHttpRequest
//AJAX默认返回文本数据,即responseType = 'text'
xhr.responseType = 'arraybuffer'
返回的response就是ArrayBuffer类型。
2)WebSockets
可直接发送或接收二进制数据。
const typedArray = new Uint8Array(4);
socket.send(typedArray.buffer);
3)Fetch API
从response直接读取arrayBuffer:
response.arrayBuffer()
Response的下列方法返回Promise对象:
arrayBuffer()
blob()
json()
text()
formData()
4)File API
需要使用FileReader:
const fileInput = document.getElementById('fileInput');
const file = fileInput.files[0];
const reader = new FileReader();
reader.readAsArrayBuffer(file);
reader.onload = function () {
const arrayBuffer = reader.result;
// ···
};
主要方法:
readAsText()
readAsDataURL() //dataUrl形式
readAsArrayBuffer()
abort()
5)Canvas
Blob
表示一个不可变、原始数据的类文件对象。
注意:File 接口基于Blob,继承了Blob的功能并将其扩展使其支持用户系统上的文件。
语法
var aBlob = new Blob(array, options);
array: [ArrayBuffer | ArrayBufferView | Blob | String]
options:{type}
;type指定mineType,即mediaType
举例:
//使用字符串创建blob
var debug = {hello: "world"};
var blob = new Blob([JSON.stringify(debug, null, 2)], {type : 'application/json'});
Blob与ArrayBuffer
blob除了原始字节外,提供了mine type
作为元数据。
//arrayBuffer转blob
let v8 = new Int8Array([1,2]);
let blob = new Blob([v8]);
// blob转arrayBuffer
blob.arrayBuffer().then(buffer=> console.log(buffer))
//or
new Response(blob).arrayBuffer().then(buffer=>console.log(buffer))
//or
var reader = new FileReader();
reader.addEventListener("loadend", function() {});
reader.readAsArrayBuffer(blob);
Blob方法及属性
size //所包含数据的字节数
type //文件mediaType
.slice([start[, end[, contentType]]]) //返回一个新的Blob
//返回一个Promise
.stream()
.text()
.arrayBuffer()
createObjectURL
//会产生一个类似 blob:d3958f5c-0777-0845-9dcf-2cb28783acaf 这样的URL字符串
let url = URL.createObjectURL(myBlob);
//可以当做普通url使用
img.src = url;
参数object:File对象
、Blob对象
或者mediaSource对象
.
实践
示例1:上传本地图片并在网页上展示(预览)
本地上传图片 -> input[type=file]获取的File对象 -> FileReader
1)-> Blob -> createObjectURL //ObjectUrL
2)-> readAsDataURL() //DataUrL
示例2:如何拼接两个音频
fetch请求音频资源 -> ArrayBuffer -> TypeArray -> set方法拼接成一个TypeArray -> ArrayBuffer -> Blob -> Object URL
示例3:json数据转化为demo.json并下载文件
1) Text -> DataURL //设置mediaType为application/octet-stream
2) Text -> Blob -> Object URL //使用a标签download
示例4:大文件分片上传
//获取文件
let blob = document.getElementById("file").files[0];
获取文件大下
let fileSize = blob.size;
//假设每片2M
let perSize = 1024*1024*2;
//得到分片数
totalPieces = Math.ceil(filesize/perSize);
//分割文件发送
start = 0;
while(start < filesize) {
end = start + perSize;
//边界处理
if(end > filesize) { end = filesize };
let chunk = blob.slice(start,end);
start = end;
formData.append(name, chunk, blob.name);
//发送文件chunk...
}
示例5:node层如何解析出文件
node层如何从multipart/form-data
类型的request读文件,关键点还是使用开始分隔符:--boundary
和结束分隔符:--boundary--
做内容分割,然后使用Content-Disposition
识别文件。
如果对你有帮助,请点赞
or在看
,谢谢~
获取更多技术干货,欢迎【扫码关注】~
参考:
https://developer.mozilla.org...
https://developer.mozilla.org...
https://developer.mozilla.org...
https://es6.ruanyifeng.com/#d...
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。