前端進階:二進位制資料的操控—-附專案程式碼

單通發表於2019-02-16

前端進階,起飛遠航

引言: 近期,工作中發現,有些前端小夥伴很少接觸到二進位制資料,所以將專案中二進位制資料的應用和大家分享一下,適合入門瞭解,高手慎入,歡迎拍磚。

專案背景: 基於公司原有圖形處理的二進位制資料檔案(公司自定義的二進位制資料格式),實現Canvas繪圖。

話說:專案開始的時候我也是一臉懵逼,這麼多技術難點需要攻克,1. 如何請求二進位制資料流?2. 如何解壓二進位制資料?3. 如何讀取二進位制資料?……


接下來我們一一攻破:見招拆招

1. 資料請求arraybuffer

基於ajax請求,設定接受的資料格式為arraybuffer型別,基於流檔案的讀取是需要非同步來處理的,否則資料可能有丟失。

  let oReq = null;
  if (window.XMLHttpRequest) {
    oReq = new XMLHttpRequest();
  }
  else {
    oReq = new ActiveXObject(`Microsoft.XMLHTTP`);
  }

  oReq.onprogress = this.updateProgress;//下載進度;
  oReq.responseType = "arraybuffer";
  oReq.onload = function () {
  // 資料下載完成會觸發;
    if ((oReq.status >= 200 && oReq.status < 300) || oReq.status == 304) {
      var arrayBuffer = oReq.response;
      // 接下來的任務
      ReadFromByteArray(arrayBuffer); //讀取收到的資料
    }
    if (oReq.status === 404) {
      alert("找不到對應檔案!")
    }
  };

  oReq.open("GET", reqUrl, true);
  oReq.send(null);

2. 資料讀取ArrayBufferDataView

ArrayBuffer 物件用來表示通用的、固定長度的原始二進位制資料緩衝區。ArrayBuffer 不能直接操作,而是要通過型別陣列物件或 DataView 物件來操作,它們會將緩衝區中的資料表示為特定的格式,並通過這些格式來讀寫緩衝區的內容。

2.1 校驗資料的大小

function ReadFromByteArray(buffer){
  parseInt(buffer.byteLength / 1024);//檔案大小,單位KB;
  
  if (buffer.byteLength < 64) {
     // 失敗
     console.log("檔案格式不對:長度小於64");
     return false;
     } else {
     // 成功 解壓資料
   }
}

DataView 檢視是一個可以從 ArrayBuffer 物件中讀寫多種數值型別的底層介面,在讀寫時不用考慮平臺位元組序問題。

接下來我們可以建立一個DataView 物件例項,此方法適合順序儲存的資料讀取,非順序(如增量式儲存的資料不能按順序讀取,需要安裝table中索引讀取,否則會讀錯)。js提供了基本的二進位制讀取API,為了不用手動計算偏移量,我們可以對基礎API進行封裝,

DataView常用讀取資料的API

getFloat32()
getFloat64()
getInt16()
getInt32()
getInt8()
getUint16()
getUint32()
getUint8()

2.2 校驗檔名稱

let dataView = new DataView(buffer, 0); //將上面獲取的buffer傳入到檢視中
let headstr = headerFiler.ReadUTFBytes(5);//讀取5個UTF8位元組,結果為檔案格式

if (headstr != "DWG") { 
//DWG為檔案的格式,存放在資料結構的頭部
 return false;
}else{
// 繼續讀取資料

}
       

3. 資料解壓TypedArraypako.js

一個TypedArray 物件描述一個底層的二進位制資料快取區的一個類似陣列(array-like)檢視。事實上,沒有名為 TypedArray的全域性物件,也沒有一個名為的 TypedArray建構函式。相反,有許多不同的全域性物件,下面會列出這些針對特定元素型別的型別化陣列的建構函式。在下面的頁面中,你會找到一些不管什麼型別都公用的屬性和方法。

為了減少資料的傳入,後端會對二進位制資料進行壓縮,前端難道要手寫解壓程式碼?就算你敢寫,你是否敢用?當然尋找三方外掛,關於js二進位制資料的解壓外掛還真不多,我選用了pako.js,移動端暫為發現嚴重相容性問題,PC端(IE)存在,一定慎用。期待推薦更加三方。

3.1 建立TypedArray

先將buffer轉換為型別陣列TypedArray,以便讀取和操控。

let compressdata = new Uint8Array(buffer, byteOffset, length);//把上面獲取`buffer`轉換成可操控的`TypedArray`。建立一個無符號整型的TypedArray,偏移量為byteOffset,長度為length。

Tips:偏移量為byteOffset類似於陣列的索引,預設為0, 設定後,從此開始讀取。如:

const compressdata = new Uint8Array(buffer, 4, 10);//從第4個位元組開始讀取,長度為10個位元組

3.2 解壓資料

利用pako.js解壓資料

let uncompress = pako.inflate(compressdata);//解壓資料;
let uncompressdata = uncompress.buffer;// ArrayBuffer {}
let dataViewData = new DataView(uncompressdata, 0);//解壓後資料;

Tips:js中的number資料型別,無論數字的大小,都將佔用8個位元組,即64位,就是Java 中double型別的長度;1字串會佔用2位元組,即16位。
js中此種規定,省去了我們宣告變數時對資料大小的計算,方便使用,但是,這樣就會造成浪費大量的儲存空間,明顯增大資料的大小。及其不便於大資料的傳輸,所以會對資料進行壓縮。

封裝資料讀取的API,避免手動計算偏移量

function WsFiler(dataView) {
    this.dataView = dataView;
    this.dataView.position = 0;
}

WsFiler.SEEK_BEGIN = 0;
WsFiler.SEEK_SET = 0;
WsFiler.SEEK_CUR = 1;
WsFiler.SEEK_END = 2;

WsFiler.prototype.ReadByte = function () {
    var b = this.dataView.getUint8(this.dataView.position);
    this.dataView.position++;
    return b;
}

WsFiler.prototype.ReadShort = function () {
    var s = this.dataView.getInt16(this.dataView.position, true);
    this.dataView.position += 2;
    return s;
};

WsFiler.prototype.ReadInt32 = function () {
    var int32 = this.dataView.getInt32(this.dataView.position, true);
    this.dataView.position += 4;
    return int32;
};

WsFiler.prototype.ReadUInt32 = function () {
    var uint32 = this.dataView.getUint32(this.dataView.position, true);
    this.dataView.position += 4;
    return uint32;
}
WsFiler.prototype.ReadUtf8String = function () {
    var len = this.ReadInt32();//字串長度;

    return this.ReadUTFBytes(len);
};

WsFiler.prototype.ReadFloat = function () {
    var ret = this.dataView.getFloat32(this.dataView.position, true);
    this.dataView.position += 4;
    return ret;
};

WsFiler.prototype.ReadDouble = function () {
    var ret = this.dataView.getFloat64(this.dataView.position, true);
    this.dataView.position += 8;
    return ret;
}

4. 資料儲存:

讀取到的資料可以任意操控,可以建立一個陣列進行儲存。便於我們的後續操控。資料的儲存就相對簡單了,根據需要將資料拆分即可。


這樣,我們就完成了二進位制資料的請求、解壓、讀取和儲存了。

後續繼續分享,用canvas把我們讀到的資料畫到網頁上。

歡迎大家拍磚。不勝感謝!

參考文獻:MDN

  1. https://developer.mozilla.org…
  2. https://developer.mozilla.org…
  3. https://developer.mozilla.org…

相關文章