ArrayBuffer簡析


關鍵技術: JavaScript,ArrayBuffer,Type Array,DataView,Web Worker,性能對比
ArrayBuffer

在文章開頭列出了這些關鍵字,主要就是讓大家了解本文的主要內容,如果你不感興趣轉發了就可以走;如果對這一塊非常了解,歡迎多提意見多交流;如果想這方面的技術一見鍾情,那不妨坐下了可以享受閱讀的樂趣。

首先,為什么Web開發者需要不斷優化數據的傳輸?因為數據是應用的核心,因這一塊直接決定了用戶體驗的好與壞,而用戶的本性是貪婪的。用戶的需求隨着自身滿意度的不斷膨脹,往往會導致這種喪心病狂的需求:“C/S下有這個效果(功能),B/S下為什么不可以?”。以前你可以笑一下,然后一副沒事請掛機的表情,但隨着HTML5標准的普及,媽的技術上真的可行了。HTML5提供了Canvas,WebGL,WebSocket,音視頻等諸多功能,完全就是一套基於瀏覽器的操作系統API。這是一個很大的成就,所帶來的沖擊是巨大的,連Adobe也都全面擁抱HTML5了,每一個Web開發者也要跟上時代的腳步。

不管你用了HTML5的哪個功能,數據都是核心的問題,特別是大數據時代,更要我們用一個新的眼光來看待數據,而隨着硬件的成熟,特別是HTML5功能的豐富,很多以前做不到的體驗現在都可以了,這也直接導致了數據的需求變得原來越大。比如音視頻,還是三維模型,上萬條數據的傳輸,如果還用傳統的json,xml這種形式,數據量稍大一些就難堪重任了,這問題無法回避。因此,怎么解決這種大數據的傳輸性能?答案很簡單,向CS取經!

1. 創建和讀寫

       傳統CS下文件基本都是二進制格式,再加上zip壓縮,短小精干,系統IO處理能力強,所以在數據量很大的情況下也可以勝任。最初在WebGL中也有類似的需求,JS和顯卡之間大量實時的數據交換,而數據通信又必須是二進制的,JavaScript也需要這樣一種有效訪問二進制的方式,便產生的類型化數組。

       ArrayBuffer本身就是一塊內存,可供用戶讀寫,使用方式也一樣簡單:

// 創建16個字節的內存
var buffer = new ArrayBuffer(16); 
// 用32位的類型來綁定該內存區域,32位,每個變量是4個字節
var int32View = new Int32Array(buffer); 
// 此時長度為4:4個int32類型,則4*4 = 16字節
for (var i=0; i<int32View.length; i++) { 
     int32View[i] = i; // 對每一個int32的變量賦值
}

       可以看到用法都差不多,但可以讓用戶實現字節級別的處理能力。當然,new不是我們的重點,重點是如何在XMLHttpRequest請求中使用ArrayBuffer方式,和服務器進行二進制的傳輸方式。

var loadArrayBuffer = function(url, headers) {        return loadWithXhr({
        url : url,        // 告訴服務器,返回類型采用arraybuffer
        responseType : 'arraybuffer', 
        headers : headers
    });
};

       OK,可想而知,相同信息下二進制則更為緊湊。下面是相同數據下大小對比,可以粗略的認為兩者之間的大小比為四倍。

1

2. 數據解析

       下面問題來了,二進制文件,看上去很壓力?確實這是一個問題。《Unix編程藝術》里面會有這樣一句話:“如果你想要創建一個新的二進制格式,那你應該睡一覺,第二天起來再好好想清楚是否有必要這樣做。”這也是Web開發者不得不面對的問題,如果JSON已經無法滿足你的需要,就要像C/S開發者一樣對二進制了然於心,誰也沒說二進制是C/S開發者的專屬,走的路多了一點而已。當然,JS中也提供了讀寫ArrayBuffer的方式。

       有下面兩個方式,一個是DateView,一個是Type Array。

DataView

       DateView API截圖

type array

Type Array具體類型

       如圖是兩者風格上的不同,嚴格說,完全使用一種也能實現解析,不同處在於前者主要是提供了函數的形式,而后者主要是以變量的形式。個人經驗是搭配使用效果更佳,一個是小家碧玉,一個是大家閨秀,各有各的好啊。一片連續的數據,比如VBO之類的就用TypeArray直接對應float類型,而對於多個屬性變量組成的結構體,可以通過DataView有序解析。好吧,完全靠感覺,下面的代碼,自己來找找感覺吧。

var pos = 0;var view = new DataView(buffer);
var minimumHeight = view.getFloat32(pos, true);
pos += Float32Array.BYTES_PER_ELEMENT;
var vertexCount = view.getUint32(pos, true);
pos += Uint32Array.BYTES_PER_ELEMENT;
var encodedVertexBuffer = new Uint16Array(buffer, pos, vertexCount * 3);

       如上是一段實際應用中的代碼,DataView封裝buffer,然后提供了基本的函數getFloat32、getUint32來實現對其中變量的逐次讀取。同時對於VertexBuffer這樣的大塊類型則用了Uint16Array直接獲取。

       可見,二進制的解析關鍵是對二進制格式的清晰,而覺得解析二進制復雜,主要還是得克服心理的作用。

這里有兩處需要強調,第一就是提倡大家使用BYTES_PER_ELEMENT,每一個Type Array都會有這個屬性來記錄長度,萬一以后該變量長度變化,而你代碼寫死了(可能性為0),你哭都來不及。可能強迫症吧,覺得這樣好。另外就是要注意Uint16Array構造函數中的參數,其中pos是字節單位,而VertexCount的單位則是Uint16,兩個字節,兩者的單位是不同的,自己到底要移動多少自己,一定要謹慎處理。

performance

不同數組類型操作運算符的性能對比

performance ie

IE下讀寫操作對比

performance chrome

Chrome下讀寫操作對比

       上面是在我筆記本下的性能對比圖(create,read&wirte),ArrayBuffer的創建速度幾乎是Array的四倍以上;讀操作快了一倍;但Array的寫操作簡直是神速;另外不同的類型下Byte,INT的差別並不大;另外IE相比Chrome簡直慢成鬼了。看來不同配置,不同瀏覽器差別還是非常大的。看來有能力還是看看JS引擎的實現,又有很多可以漲知識的地方了。

       再說一下不太常用,但也是非常好的一種使用方式。IMG標簽的形式,有些時候因為各種原因,會把二進制信息作為圖片的像素存儲,這樣通過img標簽來傳輸,方便快捷,而且有一定的加密性,對應的是Canvas的ImageData。但在客戶端就需要一個IMG轉為Type Array的一個過程,思路也不麻煩,通過ImageData來做中間過度:

//假設是服務端發送過來的img圖片
var imgInfo = new Image;
// 將該圖片繪制到canvas上context.drawImage(imgInfo,0,0,width,height);
// 獲取該Canvas里面的像素
var imgData = context.getImageData(0,0,width,height);
// 其為uint8clampedArray
var typeArray = imgData.data

       另外,二進制的問題其實還沒有這么簡單,還有字節大小端和字節對齊的問題。字節大小端的概念大家可以google查一下,不在此多言,DataView中提供了參數,默認是低字節排序。而字節對齊呢,則是Uint16Array中你所聲明的長度必須是該類型字節長度的整數倍,比如Uint16是兩個字節,則該長度要被2整除,否則瀏覽器會alert。

3. 數據處理:Web Worker

       很有意思的一個地方是,JavaScript支持異步,但本質是單線程環境,以往我們都采用setTimeout的方法來模擬實時性。而對於CS的開發者而言,多線程是處理大數據的有效手段。舉個例子,當數據量很大的時候,如何在數據處理的同時避免UI響應停滯,通常我們都是開辟一個工作者線程來處理數據,處理后的數據都放在共享池中,這時UI主線程直接使用數據,保證界面響應的順暢,而JavaScript對此無能無力,即使采用Ajax也只能局部更新,只是“看上去有了響應,但總體時間還是不變,甚至會變慢”,HTML5中提供了Web Worker的多線程機制,則可以很好的解決這個問題。

       為什么要提到Web Worker呢,因為往往數據解析后,則會進入數據處理的過程,比如解析后的數據構建三角網,或者對數據進行解壓縮,解碼等操作,如果放在主線程上處理總是不太完美的方案,這樣自然就會想到使用工作者線程Web Worker來處理。而且目前Google Earth的WebGL版本也在用Worker來處理數據,而Baidu的3D地圖還沒有,深入研究會發現很多技術上有意思的區別。這塊以后會有詳細介紹,因為也和數據有關系,這里只是開個頭涉及一下。

下面例子比較簡單,但個人感覺真要實現功能還是有很多限制,設計上也有很多技巧,所以也不多說了,多線程還是得多做才能積累經驗,給出下面這個簡單的例子,讓大家有一個簡單的了解。

主腳本:

var worker = new Worker('doWork.js');

worker.addEventListener('message', function(e) {  
   console.log('Worker said: ', e.data);
}, false);

worker.postMessage('Hello World'); // 把數據傳給工作者線程.

doWork.js (Worker):

self.addEventListener('message', function(e) {
    self.postMessage(e.data);
}, false);
4. 數據渲染

      本來這個跟本節內容無關,但為了說明一個數據自始至終的過程,所以加進來吧。WebGL硬件加速,直接使用顯卡批次渲染,是我知道的唯一的大數據渲染的一種方式,因為對其他大數據下高性能渲染還沒研究,這里只提供WebGL一種思路。

5. 其他

1.異步

       JS中數據一般都在服務器上,數據的傳輸也為異步,不同於CS多數情況下都在本地直接加載,這樣在調度上的復雜性會加大,而瀏覽器TCP連接數也有限制,所以同時請求的數目應該有所控制,服務器網卡帶寬也是一個瓶頸,通過跨域,多個IP來增大同時下載的數據量;這樣,可能你還會采用zip壓縮,提高瀏覽器緩存的復用度,要考慮的點很多,實踐性也很強。所以在設計時也應有所考慮。封裝一個合理的Primise模式會增加代碼的可閱讀性。

2.數據安全

       JS代碼雖然可以混淆,但是在客戶端還是可以調試。換句話說,通過閱讀你的JS源碼還是能夠獲取你的數據格式的。而數據往往都是核心的,二進制的數據很多情況下並不想讓用戶知道里面的結構,但很遺憾,這在JS技術本身無法對數據保密,所以只能另辟蹊徑。個人覺得有兩個可能,一個是服務端的授權,Token的方式。另一個是在數據里面增加一些冗余信息,作為自己數據的一個特有標志,如果其他人盜用數據時,這些就是版權證據。比如地圖廠商往往會在地圖上加一些特有的不存在的位置點,如果其他廠商使用了,說明他們沒有考察真實性而直接盜用數據,這就是一個證據。

.總結

       HTML5有很多很好的特性,對Web開發也是一個極大的挑戰,但單純從技術上來說是充滿誘惑的,而在大數據時代,其實這些都是很好的B/S上大數據的解決思路和基本技術。我對大數據並沒有什么研究,以我現有的膚淺了解,我認為一個Web應用如果沒有上面這些二進制,硬件加速方面的技術應用,都不能稱為大數據技術,充其量最多不過一些策略上的優化,時間和空間,服務器和客戶端之間的平衡,而不是在質的角度解決問題。

QQ圖片20151207212502
       這讓我想到了上面這個圖(盜用同學公司)。不要輕易的否定自己認為不可能的事。技術的革新總會填補某種不存在。

       如下是最高自由落體跳傘世界紀錄保持者(對,就是那位穿着宇航服跳傘,速度超過音速的Google高管)面對女兒提的一個問題,三次不同的答復:

It is impossible.

Even it is not impossible, it is very very hard.

Well, maybe it is not hard, it is just I do not know.

公眾號


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM