浅谈blob和arraybuffer

Posted by Cycle263 on June 6, 2018

Blob vs ArrayBuffer vs TypeArray

二进制数组是JavaScript操作二进制数据的一个接口。(ArrayBuffer对象、TypedArray视图和DataView视图)。三者之间的关系可以理解为:Blob <-> ArrayBuffer <-> TypeArray <—> Array

  • Blob对象:是现代浏览器中提供的能够装载二进制流(文件)的容器对象,该对象代表了一段二进制数据,提供了一系列操作接口。其他操作二进制数据的API(比如File对象),都是建立在Blob对象基础上的,继承了它的属性和方法。

  • ArrayBuffer对象:是能够装载Blob(二进制流)数据的原始缓冲区,代表内存之中的一段二进制数据,ArrayBuffer不能直接通过js读写,但是可以通过“视图”进行操作。“视图”部署了数组接口,这意味着,可以用数组的方法操作内存。

  • TypedArray视图:共包括9种类型的视图,比如Uint8Array(无符号8位整数)数组视图, Int16Array(16位整数)数组视图, Float32Array(32位浮点数)数组视图等等。

  • DataView视图:可以自定义复合格式的视图,比如第一个字节是Uint8(无符号8位整数)、第二、三个字节是Int16(16位整数)、第四个字节开始是Float32(32位浮点数)等等,此外还可以自定义字节序。

简单说,ArrayBuffer对象代表原始的二进制数据,TypedArray视图用来读写简单类型的二进制数据,DataView视图用来读写复杂类型的二进制数据。

  • websocket接受arraybuffer

    /* websocket的情况下二进制流的获取 */
    var url = 'ws://127.0.0.1:8080';
    var ws = new WebSocket(url);
    ws.binaryType = 'arraybuffer'
    ws.onmessage = function (e) {
        var data = e.data;
    }
    

格式转换

  • blob <-> arraybuffer

    var blob = new Blob(); // blob是要转换的blob
    var fr = new FileReader();
    fr.onload = function(){
        var result = this.result; // result是转换后的结果
    }
    fr.readAsArrayBuffer(blob);
    
    
    new Blob(Array[, typeObject])   // array 是一个由ArrayBuffer, ArrayBufferView, Blob, DOMString 等对象构成的 Array, typeObject为MIME类型
    
  • arraybuffer <-> Uint8

    var u8 = new Uint8Array(arraybuffer);
    var buffer = u8.buffer;
    
  • array -> arraybuffer

    var arr = [0x15,0xFF,0x01,0x00,0x34,0xAB,0x11];
    var u8 = new Uint8Array(arr);
    var ab = u8.buffer;
    
  • Float32 -> Int16

    // method 1
    while (l--) {
      s = Math.max(-1, Math.min(1, samples[l]));  // -1, 0, 1
      buf[l] = s < 0 ? s * 0x8000 : s * 0x7FFF;
    }
    
    // method 2
    while(i < len)
      dataAsInt16Array[i] = convert(data[i++]);
    function convert(n) {
      var v = n < 0 ? n * 32768 : n * 32767;       // convert in range [-32768, 32767]
      return Math.max(-32768, Math.min(32768, v)); // clamp
    }
    
  • Int16 -> Int8

    var dataAsInt8Array = new Int8Array(int16Arr);
    var dataAsInt8Array = Int8Array.from(int16Arr);
    
  • Uint8Array -> Float32Array

    var convertBlock(incomingData) { // incoming data is a UInt8Array
      var i, l = incomingData.length;
      var outputData = new Float32Array(incomingData.length);
      for (i = 0; i < l; i++) {
          outputData[i] = (incomingData[i] - 128) / 128.0;
      }
      return outputData;
    }
    
  • Int16Array -> AudioBuffer

    function int16ToAudioBuffer(input) {
      var myArrayBuffer = audioCtx.createBuffer(1, input.length, audioCtx.sampleRate);
      var nowBuffering = myArrayBuffer.getChannelData(0);
      for (var i = 0; i < input.length; i++) {
        nowBuffering[i] = input[i];
      }
      return myArrayBuffer;
    }
    
  • arrayBuffer <-> base64

    // arrayBuffer to Base64
    function _arrayBufferToBase64( buffer ) {
        var binary = '';
        var bytes = new Uint8Array( buffer );
        var len = bytes.byteLength;
        for (var i = 0; i < len; i++) {
            binary += String.fromCharCode( bytes[ i ] );
        }
        return window.btoa( binary );
    }
    // or
    // arrayBuffer to Base64
    var base64String = btoa(String.fromCharCode.apply(null, new Uint8Array(arrayBuffer)));
    // or ES6
    // arrayBuffer to Base64
    let base64String = btoa(String.fromCharCode(...new Uint8Array(arrayBuffer)));
    
    image.src = 'data:image/gif;base64,' + base64String;
    
  • base64 <-> unicode

    function b64EncodeUnicode(str) {
      return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g,
          function toSolidBytes(match, p1) {
              return String.fromCharCode('0x' + p1);
      }));
    }
    
    b64EncodeUnicode('✓ à la mode'); // "4pyTIMOgIGxhIG1vZGU="
    b64EncodeUnicode('\n'); // "Cg=="
    
    function b64DecodeUnicode(str) {
      return decodeURIComponent(atob(str).split('').map(function(c) {
          return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
      }).join(''));
    }
    
    b64DecodeUnicode('4pyTIMOgIGxhIG1vZGU='); // "✓ à la mode"
    b64DecodeUnicode('Cg=='); // "\n"
    
  • arrayBuffer <-> string

    // arrayBuffer to string
    function ab2str(buf) {
      return String.fromCharCode.apply(null, new Uint16Array(buf));
    }
    
    // string to arrayBuffer
    function str2ab(str) {
      var buf = new ArrayBuffer(str.length*2); // 2 bytes for each char
      var bufView = new Uint16Array(buf);
      for (var i=0, strLen=str.length; i<strLen; i++) {
        bufView[i] = str.charCodeAt(i);
      }
      return buf;
    }
    
  • string <-> blob

    // string to blob
    function str2ab(str){
      var byteNumbers = new Array(str.length);
      for (var i = 0; i < str.length; i++) {
          byteNumbers[i] = str.charCodeAt(i);
      }
      return new Blob(byteNumbers, {type: 'image/png'});
    }
    
  • base64 <-> blob

    // dataURL(base64)转换为Blob对象
    function dataURLtoBlob(dataurl) {
        var arr = dataurl.split(','),
          mime = arr[0].match(/:(.*?);/)[1],
          bstr = atob(arr[1]),
          n = bstr.length,
          u8arr = new Uint8Array(n);
        while(n--){
            u8arr[n] = bstr.charCodeAt(n);
        }
        return new Blob([u8arr], {type: mime});
    }
    //test:
    var blob = dataURLtoBlob('data:text/plain;base64,YWFhYWFhYQ==');
    
    // dataURL(base64)转换为Blob对象
    function b64toBlob(b64Data, contentType, sliceSize) {
      contentType = contentType || '';
      sliceSize = sliceSize || 512;
    
      var byteCharacters = atob(b64Data);
      var byteArrays = [];
    
      for (var offset = 0; offset < byteCharacters.length; offset += sliceSize) {
        var slice = byteCharacters.slice(offset, offset + sliceSize);
        var byteNumbers = new Array(slice.length);
        for (var i = 0; i < slice.length; i++) {
          byteNumbers[i] = slice.charCodeAt(i);
        }
        var byteArray = new Uint8Array(byteNumbers);
        byteArrays.push(byteArray);
      }
    
      var blob = new Blob(byteArrays, {type: contentType});
      return blob;
    }
    
    // Blob对象 to dataURL-base64
    function blobToDataURL(blob, callback) {
        var fr = new FileReader();
        fr.onload = function (e) { callback(e.target.result); }
        fr.readAsDataURL(blob);
    }
    
  • blob -> file

    // blob to file
    function blobToFile(theBlob, fileName){
        //A Blob() is almost a File() - it's just missing the two properties below which we will add
        theBlob.lastModifiedDate = new Date();
        theBlob.name = fileName;
        return theBlob;
    }
    
    // or blob to file
    var file = new File([myBlob], "name");
    

其他概念

  • buffer

    为数据缓冲对象,是一个类似数组结构的对象,可以通过指定开始写入的位置及写入的数据长度,往其中写入二进制数据。

  • stream

    是对buffer对象的高级封装,其操作的底层还是buffer对象,stream可以设置为可读、可写,或者即可读也可写,在nodejs中继承了EventEmitter接口,可以监听读入、写入的过程。具体实现有文件流,httpresponse等~~

  • 整数在计算机中的表示

    一个整数可能占 1 个、2 个或 4 个字节,即 8 个、16 个或 32 个二进制位。整数还分无符号数和有符号数。无符号数的所有二进制位都用于表示数值,于是 n 位无符号数的范围就是 0 到2^n-1,例如 8 位无符号数的范围是 0 ~ 255。有符号数则把最高位用作符号位,0 表示正数(或 0),1 表示负数。剩下的 n-1 位用于表示数值,正数直接表示,而负数则用「补码」表示 —— 负数-a的这 n-1 位的值是2^{n-1} - a。因此,n 位有符号数的范围是-2^{n-1}到2^{n-1} - 1,例如 8 位有符号数的范围是 -128 ~ 127。

  举几个例子。正数 233 的二进制形式是 11101001,它用不同长度的无符号数和有符号数的表示如下图,红色的 0 表示符号 位。注意图中没有 8 位有符号数,因为 233 超出了 8 位有符号数的范围。

整数在计算机中的表示

再如,负数 -23 用不同长度的有符号数的表示如下图,红色的 1 表示符号位。-23 用 8 位有符号数表示的形式跟 233 用 8 位无符号数表示的形式是一样的,请读者自行验证。

整数在计算机中的表示

  • 浮点数在计算机中的表示

    int型整数变量,值为9(二进制写法为1001)。普通的32位计算机,用4个字节表示int变量,所以9就被保存为00000000 00000000 00000000 00001001,写成16进制就是0x00000009。

    根据国际标准IEEE 754,任意一个二进制浮点数V可以表示成下面的形式:

  -(1)(-1)^s表示符号位,当s=0,V为正数;当s=1,V为负数。

  -(2)M表示有效数字,大于等于1,小于2。

  -(3)2^E表示指数位。

十进制的-5.0,写成二进制是-101.0,相当于-1.01×2^2。那么,s=1,M=1.01,E=2。

IEEE 754规定,对于32位的浮点数,最高的1位是符号位s,接着的8位是指数E,剩下的23位为有效数字M。对于64位的浮点数,最高的1位是符号位S,接着的11位是指数E,剩下的52位为有效数字M