0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫(xiě)文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

二進(jìn)制數(shù)據(jù)在JS程序里的表達(dá)

冬至子 ? 來(lái)源:劉驥 ? 作者:劉驥 ? 2023-07-07 14:46 ? 次閱讀

因?yàn)殡S著XHR2和現(xiàn)代瀏覽器的普及,在瀏覽器當(dāng)中處理二進(jìn)制不再向過(guò)去那樣無(wú)所適從,隨著Canvas/WebGL等新技術(shù)逐漸開(kāi)始進(jìn)入大眾視野,也會(huì)用到一些字節(jié)數(shù)組或者16位、8位整數(shù)等東西。在node.js剛剛發(fā)布的4.0版本中,Buffer的底層使用了更符合JS標(biāo)準(zhǔn)的Uint8Array來(lái)實(shí)現(xiàn),瀏覽器和node.js再次向相同的目標(biāo)靠近了一點(diǎn)點(diǎn),所以對(duì)于JS中處理二進(jìn)制,我就打算寫(xiě)這篇文章作一個(gè)入門(mén)性質(zhì)的流水賬,方便一些對(duì)二進(jìn)制處理不了解的同學(xué)快速入門(mén),雖然在前端領(lǐng)域用到的不多,不過(guò)也可以作為茶余飯后的休閑談資。

二進(jìn)制數(shù)據(jù)在JS程序里的表達(dá)

現(xiàn)今世界上幾乎所有的計(jì)算機(jī)體系結(jié)構(gòu)都是以字節(jié)(byte)為二進(jìn)制數(shù)據(jù)的基本單位(注:不是說(shuō)最小單位),所以二進(jìn)制常常以字節(jié)數(shù)組的形式存在于程序當(dāng)中。例如在C#里面,就用byte[],標(biāo)準(zhǔn)C里面沒(méi)有byte類型,但可以通過(guò)typedef把byte定義為unsigned char的別名,效果是一樣的。

JS設(shè)計(jì)之初似乎根本沒(méi)想過(guò)要處理二進(jìn)制的東西,加上對(duì)類型的極度弱化,對(duì)于字節(jié)的概念可以說(shuō)是非常非常的模糊。如果要表達(dá)字節(jié)數(shù)組,那么似乎只能用一個(gè)普通數(shù)組來(lái)表示。

HTML5體系引入了一大堆新的東西,比如XHR2,是可以上傳或下載二進(jìn)制內(nèi)容的,與之配套的東西就是JS里的ArrayBuffer和Typed Array了。

ArrayBuffer是一個(gè)固定長(zhǎng)度的字節(jié)序列,你可以通過(guò)new ArrayBuffer(length)來(lái)得到一片空間,或者用下文將會(huì)介紹的方法從XHR2等途徑獲取。由于內(nèi)部實(shí)現(xiàn)與數(shù)組不一樣,ArrayBuffer通常都是連續(xù)內(nèi)存(注意,這只是經(jīng)驗(yàn)之談,并不是規(guī)范也不是文檔所明確的),因此對(duì)于高密度的訪問(wèn)操作而言它比JS中的Array速度會(huì)快很多(但并不要用它來(lái)簡(jiǎn)單地代替Array)。如果用Chrome的Profile工具查看Heap Snapshot,會(huì)發(fā)現(xiàn)ArrayBuffer會(huì)被單獨(dú)列為一類,也許它的內(nèi)存分配和布局與Array以及其他JS對(duì)象有一些差別吧。

ArrayBuffer是不能直接被訪問(wèn)的,因此需要借助Typed Array。Typed Array是一組具體數(shù)據(jù)類型的Array-Like類型的統(tǒng)稱,包括

Int8Array 8位有符號(hào)整數(shù),類似于C里面的char

Uint8Array 8位無(wú)符號(hào)整數(shù),類似于C里面的unsigned char

Uint8ClampedArray 8位無(wú)符號(hào)整數(shù),跟Uint8類似,但在溢出處理上不大一樣

Int16Array 后面這些類型就不羅嗦了

Uint16Array

Int32Array

Uint32Array

Float32Array

Float64Array

Typed Array的背后是一個(gè)ArrayBuffer,也就是說(shuō),事實(shí)上的數(shù)據(jù)是存在ArrayBuffer里面的,而Typed Array只是給你提供了一個(gè)某種類型的讀寫(xiě)接口,用MDN的話說(shuō),叫做

Multiple views on the same data

舉個(gè)栗子,如果我們有一個(gè)ArrayBuffer名為buffer(先不考慮怎么構(gòu)造這個(gè)測(cè)試數(shù)據(jù)),內(nèi)容如下:

01 02 03 04 05 06 07 08

也就是說(shuō)它有8個(gè)字節(jié),我們分別用它來(lái)構(gòu)造Uint8Array, Uint16Array, Uint32Array,則可以得到

var u8 = new Uint8Array(buffer); // length為8

var u16 = new Uint16Array(buffer); // length為4

var u32 = new Uint32Array(buffer); // length為2

它們的內(nèi)容分別為

[1, 2, 3, 4, 5, 6, 7, 8]

[513, 1027, 1541, 2055]

[67305985, 134678021]

這不難理解。

可以看出,如果要手工構(gòu)造上面的測(cè)試數(shù)據(jù)ArrayBuffer,用Uint8Array就會(huì)很方便(呃事實(shí)上這是我個(gè)人最常用的一種Typed Array)。

而如果用同樣的ArrayBuffer構(gòu)建帶符號(hào)整數(shù)類型,則可能因?yàn)檎麛?shù)溢出而得到不同的結(jié)果,上面的例子并沒(méi)有碰到,有興趣的話可以自己試試。因此使用Typed Array也可以用來(lái)做有符號(hào)數(shù)和無(wú)符號(hào)數(shù)的轉(zhuǎn)換。

如果你用過(guò)canvas的getImageData/putImageData的話,會(huì)發(fā)現(xiàn)它給你的就是一個(gè)Uint8ClampedArray,這東西訪問(wèn)起來(lái)速度比JS的原生Array快很多,使得對(duì)canvas進(jìn)行高速的像素操作成為可能。

然而最最重要的一個(gè)概念還是:Typed Array不直接存放任何數(shù)據(jù),所有對(duì)Typed Array進(jìn)行讀寫(xiě)的操作,最終都會(huì)落實(shí)到它背后所持有的ArrayBuffer的身上。ArrayBuffer才是真正的raw bytes,而Typed Array只是一個(gè)操作窗口/操作視圖(View)。

獲取二進(jìn)制數(shù)據(jù)

nodejs那邊先按住不表,這里談?wù)勗诰W(wǎng)頁(yè)里如何獲取二進(jìn)制數(shù)據(jù)?常見(jiàn)的辦法有3種,1是通過(guò)XMLHttpRequest 2,2是通過(guò)File和Blob一套相關(guān)接口。

通過(guò)XMLHttpRequest 2

XHR2的接口跟XHR幾乎是一樣的,當(dāng)制定xhr.responseType = 'arraybuffer'以后,在成功獲取數(shù)據(jù)的回調(diào)里就可以通過(guò)xhr.response來(lái)得到請(qǐng)求結(jié)果的ArrayBuffer了,然后就可以按照你的意愿來(lái)構(gòu)造各種Typed Array進(jìn)行訪問(wèn)。

responseType還可以有blob取值,可以用xhr.response獲得Blob對(duì)象。

通過(guò)File和Blob

在HTML5中提供了對(duì)表單的文件控件[ ] 更豐富的操作,可以通過(guò)inputDOM對(duì)象的.files來(lái)獲取一個(gè)FileList,當(dāng)然通常瀏覽器都只提供了單選的文件控件,于是這里都只會(huì)有一個(gè)File對(duì)象。另外,通過(guò)拖拽、剪貼板等方式也能獲取到File或者Blob。

File繼承了Blob,并提供了name, lastModifiedDate等基礎(chǔ)元數(shù)據(jù),但是依然是一個(gè)深度封裝,不能直接獲取到它的二進(jìn)制。

Blob是Binary large object的縮寫(xiě),它與ArrayBuffer的區(qū)別是除了raw bytes以外它還提供了mime type作為元數(shù)據(jù)。但它依然是無(wú)法直接被讀寫(xiě)的。

這時(shí)候需要借助FileReader的幫忙。FileReader提供了一組用來(lái)將Blob讀取為更為實(shí)用的類型的方法

readAsArrayBuffer()

readAsBinaryString()

readAsDataURL()

readAsText()

例如

var file = get_file_some_how();

var fr = new FileReader();

fr.onload = function(e) {

e.target.result; // 讀取的結(jié)果

};

fr.readAsDataUrl(file); // readAsArrayBuffer

可以干什么呢?例如圖片上傳之前的本地預(yù)覽(甚至基于canvas的編輯)等等都可以實(shí)現(xiàn)了。

Blob的其他構(gòu)造方法多而雜,這里就先不到處搬運(yùn)文檔了。

消費(fèi)二進(jìn)制數(shù)據(jù)

何謂消費(fèi)?最常見(jiàn)的方式也許就是通過(guò)XHR2直接把二進(jìn)制數(shù)據(jù)以文件方式POST到服務(wù)端去。

這里我比較推薦使用FormData來(lái)構(gòu)造POST數(shù)據(jù)。因?yàn)樵诜?wù)端收的時(shí)候會(huì)比較容易一些,具體有興趣可以去找找別人的例子。

雖然直接提交ArrayBuffer也是可以的,但是這種時(shí)候服務(wù)端收到的POST body會(huì)是一大團(tuán),用起來(lái)不方便。如果要使用FormData來(lái)提交ArrayBuffer,需要先將其構(gòu)造成Blob。

對(duì)Typed Array的構(gòu)造留個(gè)心眼

當(dāng)使用new xxxxxArray(arrayBuffer)這個(gè)重載進(jìn)行構(gòu)造的時(shí)候,它會(huì)默認(rèn)基于此ArrayBuffer進(jìn)行構(gòu)造。但當(dāng)使用new xxxxArray(another_typed_array)這個(gè)重載的時(shí)候,則是進(jìn)行“拷貝構(gòu)造”,這樣兩個(gè)Typed Array會(huì)指向不同的buffer,需要注意這是否符合預(yù)期。

如果需要基于同一個(gè)ArrayBuffer來(lái)構(gòu)造Typed Array,可以使用Typed Array的buffer, byteLength,byteOffset來(lái)獲取它背后的ArrayBuffer。

Tips(坑)

對(duì)內(nèi)存對(duì)齊留個(gè)心眼

當(dāng)使用ArrayBuffer來(lái)構(gòu)造Typed Array的時(shí)候,可以指定byteOffset參數(shù),例如

var buffer = get_array_buffer_some_how();

var i16 = new Int16Array(buffer, 10);

上面的代碼就能以buffer向后偏移10字節(jié)處為起點(diǎn)來(lái)構(gòu)造Int16Array,但是如果將10設(shè)置為一個(gè)奇數(shù),會(huì)發(fā)現(xiàn)如下錯(cuò)誤:

RangeError: start offset of Int16Array should be a multiple of 2

這是因?yàn)門(mén)yped Array對(duì)內(nèi)存對(duì)齊有要求,它不能在非對(duì)齊的位置建立,同理,Uint32Array和Int32Array則要求偏移量是4字節(jié)對(duì)齊的。

因此如果你希望在非對(duì)齊的位置進(jìn)行讀寫(xiě),則需要借助DataView的幫忙。

對(duì)字節(jié)序留個(gè)心眼

我們?nèi)粘V兴鶎?xiě)的程序,幾乎都不需要關(guān)心字節(jié)序,因此這個(gè)問(wèn)題沒(méi)那么嚴(yán)重,知道自己的程序會(huì)有字節(jié)序問(wèn)題的人,開(kāi)發(fā)到這里也肯定會(huì)知道問(wèn)題的存在,但這里還是稍微提一下。

按照MDN的說(shuō)法,Typed Array只會(huì)使用當(dāng)前平臺(tái)的字節(jié)序,例如我們現(xiàn)在用的桌面電腦不論P(yáng)C還是Mac都是x86/x64的,也就是little-endian了。

使用DataView,不僅可以解決上面說(shuō)到的內(nèi)存對(duì)齊的問(wèn)題,還可以指定讀寫(xiě)時(shí)的字節(jié)序,具體參數(shù)都在文檔里面了,就不搬運(yùn)了。

使用DataView配合Typed Array也可以做到一個(gè)檢測(cè)當(dāng)前平臺(tái)字節(jié)序的技巧:

function isLittleEndian() {

var buf = new ArrayBuffer(2);

var view = new DataView(buf);

view.setInt16(0, 256, true);//顯式以little endian寫(xiě)入數(shù)據(jù)

// 此時(shí)buf里的內(nèi)存布局應(yīng)該是 00 01

var i16 = new Int16Array(buf);

// 如果以little endian讀取,它就是256;以big endian讀取,則是1

return (i16[0] === 256);

}

如果你編寫(xiě)的程序需要垮體系結(jié)構(gòu)例如x86/ARM/PPC等,則在交換文件和網(wǎng)絡(luò)包的時(shí)候需要謹(jǐn)慎處理字節(jié)序,當(dāng)然一個(gè)辦法是在這些地方預(yù)先規(guī)范統(tǒng)一字節(jié)序以防后患。不過(guò)那些都是題外話了。

小結(jié)

使用ArrayBuffer來(lái)存儲(chǔ)一段字節(jié),使用Typed Array來(lái)構(gòu)建一個(gè)具體數(shù)值類型的訪問(wèn)窗口,使用DataView對(duì)非對(duì)齊或在乎字節(jié)序的ArrayBuffer進(jìn)行更精確的操作,使用XHR2, Blob, File, FileReader, FormData等多種方式來(lái)獲取或消費(fèi)ArrayBuffer。

另外羅嗦一句,瀏覽器還提供了一系列所謂的“Binary String”,就是一些看起來(lái)像亂碼一樣的字符串,然后又提供了atob/btoa這種方式來(lái)對(duì)Base64和“Binary String”進(jìn)行相互轉(zhuǎn)換,甚至FileReader還提供了readAsBinaryString方法(已經(jīng)廢棄了,善哉)。這個(gè)Binary String真是誰(shuí)用誰(shuí)遭殃,別問(wèn)我為什么知道……

聲明:本文內(nèi)容及配圖由入駐作者撰寫(xiě)或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 存儲(chǔ)器
    +關(guān)注

    關(guān)注

    38

    文章

    7492

    瀏覽量

    163854
  • 二進(jìn)制
    +關(guān)注

    關(guān)注

    2

    文章

    795

    瀏覽量

    41654
  • MDN
    MDN
    +關(guān)注

    關(guān)注

    0

    文章

    6

    瀏覽量

    8080
  • HTML5
    +關(guān)注

    關(guān)注

    0

    文章

    73

    瀏覽量

    17416
收藏 人收藏

    評(píng)論

    相關(guān)推薦

    鴻蒙二進(jìn)制數(shù)組創(chuàng)建

    背景 c++層數(shù)據(jù)都是二進(jìn)制,需要轉(zhuǎn)換成arrayBuffer透?jìng)鞯絜ts層給業(yè)務(wù)使用,但是鴻蒙的使用下面兩個(gè)api創(chuàng)建出來(lái)的二進(jìn)制數(shù)組數(shù)據(jù)都是錯(cuò)誤的。 接口
    的頭像 發(fā)表于 01-31 15:24 ?1277次閱讀

    二進(jìn)制相對(duì)調(diào)相(二進(jìn)制差分調(diào)相2DPSK)的工作原理

    二進(jìn)制相對(duì)調(diào)相(二進(jìn)制差分調(diào)相2DPSK)的工作原理
    發(fā)表于 10-21 13:01 ?3205次閱讀
    <b class='flag-5'>二進(jìn)制</b>相對(duì)調(diào)相(<b class='flag-5'>二進(jìn)制</b>差分調(diào)相2DPSK)的工作原理

    二進(jìn)制

    二進(jìn)制   二進(jìn)制與十進(jìn)制的區(qū)別在于數(shù)碼的個(gè)數(shù)和進(jìn)位規(guī)律有很大的區(qū)別,顧名思義,二進(jìn)制的計(jì)數(shù)規(guī)律為逢二進(jìn)一,是以2為基數(shù)的計(jì)數(shù)體制。10這
    發(fā)表于 04-06 23:48 ?8200次閱讀
    <b class='flag-5'>二進(jìn)制</b>

    二進(jìn)制編碼和二進(jìn)制數(shù)據(jù)

    二進(jìn)制編碼和二進(jìn)制數(shù)據(jù)   二進(jìn)制編碼是計(jì)算機(jī)內(nèi)使用最多的碼制,它只使用兩個(gè)基本符號(hào)"0"和"1",并且通過(guò)由這兩個(gè)符號(hào)組成的
    發(fā)表于 10-13 16:22 ?4790次閱讀

    什么是二進(jìn)制計(jì)數(shù)器,二進(jìn)制計(jì)數(shù)器原理是什么?

    什么是二進(jìn)制計(jì)數(shù)器,二進(jìn)制計(jì)數(shù)器原理是什么? 計(jì)數(shù)器是數(shù)字系統(tǒng)中用得較多的基本邏輯器件。它不僅能記錄輸入時(shí)鐘脈沖的個(gè)數(shù),還可以實(shí)現(xiàn)
    發(fā)表于 03-08 13:16 ?3.1w次閱讀

    二進(jìn)制電平,什么是二進(jìn)制電平

    二進(jìn)制電平,什么是二進(jìn)制電平 二進(jìn)制數(shù)字通信系統(tǒng)中,每個(gè)碼元或每個(gè)符號(hào)只能是“1”和“0”兩個(gè)狀態(tài)之一。若將每個(gè)碼元可能取的狀態(tài)增
    發(fā)表于 03-17 16:51 ?2359次閱讀

    進(jìn)制數(shù)的二進(jìn)制編碼

    進(jìn)制數(shù)的二進(jìn)制編碼     人機(jī)交互過(guò)程中,為了既滿足系統(tǒng)中使用二進(jìn)制數(shù)的要求,又適應(yīng)人們使用十進(jìn)制數(shù)
    發(fā)表于 05-02 19:04 ?9802次閱讀

    二進(jìn)制加法程序【匯編版】

    二進(jìn)制加法程序【匯編版】二進(jìn)制加法程序【匯編版】二進(jìn)制加法程序【匯編版】
    發(fā)表于 12-29 11:02 ?0次下載

    二進(jìn)制加法程序【C語(yǔ)言版】

    二進(jìn)制加法程序【C語(yǔ)言版】二進(jìn)制加法程序【C語(yǔ)言版】二進(jìn)制加法程序【C語(yǔ)言版】
    發(fā)表于 12-29 11:03 ?0次下載

    二進(jìn)制數(shù)據(jù)壓縮算法

    二進(jìn)制數(shù)據(jù)壓縮算法二進(jìn)制是計(jì)算技術(shù)中廣泛采用的一種數(shù)制。二進(jìn)制數(shù)據(jù)是用0和1兩個(gè)數(shù)碼來(lái)表示的數(shù)。它的基數(shù)為2,進(jìn)位規(guī)則是“逢
    的頭像 發(fā)表于 02-28 09:31 ?2w次閱讀

    二進(jìn)制加法的實(shí)現(xiàn)細(xì)節(jié)

    之前的文章,曾經(jīng)多次提到過(guò)二進(jìn)制加法的數(shù)字電路,這里詳細(xì)說(shuō)說(shuō)它的細(xì)節(jié)。
    的頭像 發(fā)表于 03-23 13:58 ?2741次閱讀
    <b class='flag-5'>二進(jìn)制</b>加法的實(shí)現(xiàn)細(xì)節(jié)

    二進(jìn)制數(shù)據(jù)及取值范圍的計(jì)算方法

    本文介紹二進(jìn)制數(shù)據(jù)的相關(guān)知識(shí),如定義、取值范圍計(jì)算、轉(zhuǎn)換為十進(jìn)制的方法以及一些常見(jiàn)位數(shù)的二進(jìn)制數(shù)據(jù)的取值范圍等。
    的頭像 發(fā)表于 11-08 15:48 ?2212次閱讀
    <b class='flag-5'>二進(jìn)制</b><b class='flag-5'>數(shù)據(jù)</b>及取值范圍的計(jì)算方法

    二進(jìn)制、八進(jìn)制、十六進(jìn)制現(xiàn)實(shí)當(dāng)中有什么意義?

    二進(jìn)制、八進(jìn)制、十六進(jìn)制現(xiàn)實(shí)當(dāng)中有什么意義? 二進(jìn)制、八進(jìn)制和十六
    的頭像 發(fā)表于 01-16 11:14 ?5444次閱讀

    如何實(shí)現(xiàn)二進(jìn)制和BCD碼數(shù)據(jù)的相互轉(zhuǎn)變?

    如何實(shí)現(xiàn)二進(jìn)制和BCD碼數(shù)據(jù)的相互轉(zhuǎn)變? 二進(jìn)制碼是將十進(jìn)制數(shù)字表示為二進(jìn)制數(shù)和十進(jìn)制數(shù)的一種表
    的頭像 發(fā)表于 02-18 14:51 ?3610次閱讀

    二進(jìn)制編碼器工作原理 如何選擇二進(jìn)制編碼器

    二進(jìn)制編碼器是一種數(shù)字電路,它將輸入的二進(jìn)制代碼轉(zhuǎn)換為對(duì)應(yīng)的輸出信號(hào)。在數(shù)字系統(tǒng)中,編碼器用于將數(shù)據(jù)從一種形式轉(zhuǎn)換為另一種形式,以便于處理和傳輸。 二進(jìn)制編碼器工作原理 輸入與輸出關(guān)系
    的頭像 發(fā)表于 11-06 09:44 ?786次閱讀