abbshr / abbshr.github.io

人们往往接受流行,不是因为想要与众不同,而是因为害怕与众不同
http://digitalpie.cf
444 stars 44 forks source link

Binary World:ArrayBuffer、Blob以及他们的应用 #28

Open abbshr opened 10 years ago

abbshr commented 10 years ago

Frontend

HTML5为我们带来不少新奇的东西,除了那几个闪亮的明星“WebSocket”、“Worker”、“Canvas”等等之外,还有几个非著名演员(包括但不限于):“Blob”、“ArrayBuffer”、“URL”、“FormData”。这些小角色是用来支持二进制字节数据操作的。但BlobArrayBuffer的分界线似乎很模糊,我们写程序时往往会纠结应该使用哪种方式来在和Server进行数据交互,所以今天我们就把他们弄明白。

ArrayBuffer

我在前几篇里简单的翻译了Node中Buffer函数和buffer对象的解释及用法。没错,_buffer_就是缓冲区的意思。“缓冲”的目之一就是为了解决设备之间I/O速度差异的问题:当一个Socket A向Socket B发送数据时,到达的比特流首先会在B的缓冲区里停留,然后再由B进行读取,如果没有这个缓冲区,B就有可能无法读取完整的数据。

当然,这里谈到的ArrayBuffer就类似刚刚所说的缓冲区。这些缓冲区都是按byte(字节)进行划分的。这个ArrayBuffer和Node环境下的Buffer很像,并且都是一个特殊的数组(将缓冲区作为数组来使用)。虽说Node里也有ArrayBuffer,不过和前端的ArrayBuffer稍有些区别,这里我们讨论的是浏览器端的ArrayBuffer。

如果缓冲区这个解释略抽象,那么就把他当成是一个用来装byte数据的容器吧。可以算是浏览器端最基础的数据格式了,请记住关键字:字节。这是用以区分后面提到的Blob的最好描述。

// 申请一个10byte的缓冲区
var af = new ArrayBuffer(10);
view

对于ArrayBuffer,HTML5还提供了几个高级的封装,用以快捷操作字节数据,我们称之为view:

比如说,Uint8Array就是利用ArrayBuffer开辟一个数组,元素类型为8bit(1byte)无符号整型的:

//10个元素的缓冲区(也就是10 * 1 byte)
var ui8arr = new Uint8Array(10);
//等价于
var af = new ArrayBuffer(10);
var ui8arr = new Uint8Array(af);

获取了view的实例,我们就可以使用实例的方法,对缓冲区进行读写。
这些view既可以像ArrayBuffer一样按数目申请空间,又可以引用已有的ArrayBuffer空间。
对于后一种情况,无论用view创建多少个实例,这些引用都是指向原始的ArrayBuffer内存地址,所以一旦对某一view的实例做了变动,其他view的实例也会变化。

note

注意:通过view对ArrayBuffer进行读写时,需要注意byte的读写方式,即“低位优先字节次序”和“高位优先字节次序”。
所谓字节次序是指占内存多于一个字节类型的数据在内存中的存放顺序
举个形象的例子。将一个4byte的数0xFE051324放入内存,如果是低优先顺序,内存中的存储如下:

内存地址减小 <—— | 24 | 13 | 05 | FE | ——> 内存地址增大

如果按照高位优先顺序来存储这一数据,在内存中的实际存放是这样的:

内存地址减小 <—— | FE | 05 | 13 | 24 | ——> 内存地址增大

正好对称过来,如果仍按照“低位次序”的方式来读取,则读出来的数据就是:0x241305FE

因此,事先知道数据的读取方式很重要。那如何按照我们所期望的方式来读取呢?还要感谢HTML5,它提供了更高级的view:DataView对象,专业操作ArrayBuffer。

DataView
var af = new ArrayBuffer(10);
//使用DataView管理ArrayBuffer

var dv = new DataView(af);

DataView的实例提供了一系列读写ArrayBuffer的方法,如:

// 从ArrayBuffer索引为2(第三个byte)的元素开始,读取16位无符号整型
dv.getUint16(2);

//默认是按“高位次序”进行读写的,如果需要改成“小端次序”:
dv.getUint16(2, true);

//“高位次序” 写操作,从索引为2开始,写入16位无符号整型0xFF
dv.setUint16(2, 0xFF, false);

具体的API这里就不写了,详见W3c草案。

Blob

Blob是当前Web前端很常用的数据格式,是Binary Large Object(大型二进制对象)的缩写。代表原始的二进制数据。和ArrayBuffer类似,都是二进制数据的容器。

//可以用字符串构建Blob
var blob = new Blob(['ran aizen on github']);

//ArrayBuffer也行
var blob = new Blob([new ArrayBuffer(10)]);

从描述上来看,ArrayBuffer似乎是Blob的底层,Blob内部使用了ArrayBuffer。并且构造好的一个Blob实体就是一个raw data。既然用途差不多,那为什么一个Blob一个ArrayBuffer呢?当然,设计Blob和ArrayBuffer的目的是不同的。因为ArrayBuffer更底层,所以它专注的是细节,比如说按字节读写文件。相反,Blob更像一个整体,它不在意细节:就是那么一个原始的Binary Data,你只要来回传输就行了。

How to use?

En, this is a good question~ 还记得ajax和WebSocket吗?如何用这两种技术去传输文件呢?Good!派上用场了~

先看看WebSocket:

var ws = new WebSocket('ws://chat.io');

//ok,我们建立了一个ws通信链接,接着看看数据是以什么格式传输的:
ws.binaryType  // -> "blob"

当然,type的类型可以在允许的范围内自定义,不过默认是blob。也就是说,Blob可以用在WebSocket通信中,并且它就是通信中的二进制数据,对应Node中的Buffer(Node中接收到的二进制数据就是Buffer的实例)。看来使用Blob可以很好地与Node Server进行交互。

使用ajax中传文件并不是件新鲜事,我们使用的网盘或云相册几乎都用了这一方式。

var xhr = new XMLHttpRequest();

//如果要得到二进制数据,一般是文件,可以设为blob
xhr.responseType = "blob";

//上传二进制数据
xhr.send(row)

在HTML5新的标准中File对象的内部就使用了Blob,从<input>标签中获取的File对象即是一个Blob实例。

blob文件的转换,可以使用FileReader对象:

var fd = new FileReader();

//fd有几个文件读取方法,可以得到ArrayBuffer、Blob或String的数据

//可以使用ArrayBuffer读取方式,得到的会是一个ArrayBuffer实例
fd.readAsArrayBuffer(file);

//这里使用了blob的方式,所以会得到一个blob对象
//fd.readAsBinaryString(file);

fd.onload = function (e) {
    //读取成功后得到ArrayBuffer
    buffer = e.target.result;
};

上面的例子中,我们将一个blob文件以ArrayBuffer的形式进行读取,得到了一个ArrayBuffer的实例。
为什么要把Blob弄成ArrayBuffer?因为这样我们就可以对文件的字节进行读写了,比如说要判断一个文件的类型,就可以读取它的前两个字节,与Hash表进行匹配,等等。
其实在C / S的交互中,发送的数据往往直接就是Binary Data, 很少需要一个底层的ArrayBuffer按byte来手动构造数据。“茫茫Web,不要在意细节嘛”~

下面还有一个使用Blob的使用例子,通过Blob构造URL:

URL对象的createObjectURL方法允许传入一个blob,并得到一个临时的URL:

//假如Server响应了一个图片
var URI = URL.createObjectURL(xhr.response);

var img = document.createElement('img');

//我们可以把blob url用在这里:
img.src = URI;
document.append(img);

至于选择Blob还是ArrayBuffer,关键要看你的目的是什么。
二进制已经融入了WEB前端世界,这里仅仅介绍了两位角色,还有好多新鲜玩意等待你探索。慢慢玩吧,骚年~

bsed commented 9 years ago

article beautify~

codezyc commented 7 years ago

👍

marsk6 commented 6 years ago

👍

yhhwpp commented 5 years ago

👍