從這篇文章你將了解到什么??
ArrayBuffer的作用?
左位移和右位移運(yùn)算的使用?
按位與運(yùn)算的使用
之前介紹了二進(jìn)制相關(guān)的知識(shí)?二進(jìn)制轉(zhuǎn)十進(jìn)制心算大法, 本篇將使用JavaScript開(kāi)發(fā)一個(gè)相關(guān)的可視化工具,實(shí)現(xiàn)十進(jìn)制和二進(jìn)制之間的自動(dòng)轉(zhuǎn)換。
當(dāng)然,醉翁之意不在酒。在開(kāi)發(fā)的過(guò)程中熟悉二進(jìn)制的位運(yùn)算才是本篇的關(guān)注點(diǎn)。
轉(zhuǎn)換工具介紹
下面來(lái)看看這個(gè)可視化工具。
輸入一個(gè)十進(jìn)制的正整數(shù)(32位無(wú)符號(hào)整數(shù)), 我們會(huì)將該數(shù)字的二進(jìn)制展示到一個(gè)表格中。
表格由32個(gè)單元格組成, 因?yàn)?字節(jié)=8字位, 所以單元格按八個(gè)一組來(lái)劃分。
比如, 輸入一個(gè)數(shù)字7, 對(duì)應(yīng)的二進(jìn)制就是111,那么表格就應(yīng)該是下面這樣:
每個(gè)單元格上的二進(jìn)制數(shù)字都可以在0和1之間切換, 對(duì)應(yīng)的也會(huì)計(jì)算出這組二進(jìn)制數(shù)代表的十進(jìn)制數(shù)。
比如, 我們把下圖選中的單元格從0切換為1,那么對(duì)應(yīng)的十進(jìn)制數(shù)也會(huì)跟著變?yōu)?63:
ArrayBuffer介紹
下面我們用數(shù)字263作為例子,講講如何使用位運(yùn)算操作字節(jié)流的方式實(shí)現(xiàn)代碼。
首先,初始化的時(shí)候,我們會(huì)把263這個(gè)數(shù)字放入一個(gè)數(shù)組,然后把這個(gè)數(shù)組轉(zhuǎn)為字節(jié)流存放到ArrayBuffer內(nèi)存當(dāng)中 。
如圖,下方的矩形ArrayBuffer表示一段內(nèi)存,但是我們不能直接操作它。
這時(shí)候我們就要用到JS里的TypedArray來(lái)訪問(wèn)這一段內(nèi)存,MDN把TypedArray稱為“Multiple views on the same data”。
我們可以使用Uint32Array,Uint8Array等等這些“View”來(lái)對(duì)ArrayBuffer內(nèi)存進(jìn)讀寫(xiě)。
這樣一來(lái),我們的“十進(jìn)制展示區(qū)” 和“二進(jìn)制展示區(qū)”都可以從同一塊內(nèi)存中讀取數(shù)據(jù),不用浪費(fèi)另外的數(shù)組空間去存放一大堆的0和1。
另外,在改變“二進(jìn)制展示區(qū)”單元格數(shù)值時(shí),我們可以直接對(duì)ArrayBuffer內(nèi)存中的數(shù)據(jù)進(jìn)行寫(xiě)操作,省去了很多麻煩。
下面請(qǐng)看代碼,我們使用Uint32Array來(lái)表示“十進(jìn)制展示區(qū)” 。
let target = new Uint32Array([263])
將數(shù)組[263]轉(zhuǎn)為字節(jié)流,再讀取為一個(gè)由32位無(wú)符號(hào)字節(jié)組成的數(shù)組,于是變量target賦值的數(shù)組就是[263]。target[0]就是圖中“十進(jìn)制展示區(qū)”的263。
我們使用Uint8Array來(lái)表示“二進(jìn)制展示區(qū)”。?
let bytes = new Uint8Array(target.buffer)
通過(guò)target.buffer可以讀取到內(nèi)存中存放的字節(jié)流,將其讀取為一個(gè)由8位無(wú)符號(hào)字節(jié)組成的數(shù)組,得到的bytes數(shù)組就是[7,1,0,0], 對(duì)應(yīng)的二進(jìn)制數(shù)組就是[0000111,00000001, 00000000, 00000000]。
接下來(lái)我們要將bytes數(shù)組顯示到“二進(jìn)制展示區(qū)”中。
因?yàn)橛?2個(gè)單元格, 我們從第0格遍歷到第31格, 每個(gè)單元格都是通過(guò)getBit方法從bytes數(shù)組中獲取對(duì)應(yīng)的二進(jìn)制數(shù)值。
function writeBits() {? ?
? ? for (var i = 0; i < 32; i++) {? ? ?
? ? ? ? 單元格[i].textContent = getBit(i);? ?
? ? }
}
下面是getBit的具體實(shí)現(xiàn)代碼。
function getBit(bit) {? ?
? ? return bytes[bit >> 3] & (0x1 << (bit & 0x7)) ? 1 : 0;
}
我們一段段來(lái)解釋下。
“bit>>3”分組
“>>”是右位移運(yùn)算符,如果n是整數(shù),那么n>>3效果等同n除以8取除數(shù),可以用于分組;
“bit&7”求余
如果n是整數(shù),n&7效果等同n除以8取余數(shù),可以用于確定n在所屬分組中的位置;
“&”運(yùn)算和“<<”運(yùn)算
我們知道按位與運(yùn)算的規(guī)則是下面這樣:
1&1=1
0&1=0
1&0=0
0&0=0
也即是說(shuō), 如果我們想要知道二進(jìn)制數(shù)字"0100 0n01"中的n是0還是1,可以這樣:
0x01000n01 & 0x00000100
如果結(jié)果等于0,那么n的值就是0;如果結(jié)果大于0,那么n的值就是1。
在按位與運(yùn)算的規(guī)則下,0x00000100就是一個(gè)“取值器”。我們可以通過(guò)左位移運(yùn)算符得到一個(gè)“取值器”。
0x1 << n的位置
把0x0000 0001中的1移動(dòng)到n的位置,也就是0x0000 0100。
例子講解
關(guān)鍵的位運(yùn)算技巧都講完之后,我們用一個(gè)例子來(lái)感受下。
比如我們要得到第2個(gè)單元格(從第0格算起)在bytes數(shù)組[0000111,00000001, 00000000, 00000000]中對(duì)應(yīng)的數(shù)值,具體的過(guò)程就是這樣:
第一步,通過(guò)2>>3=0,可以計(jì)算出第2個(gè)單元格屬于第0組,也就是bytes數(shù)組中下標(biāo)為0的元素。
第二步,通過(guò)2&7=0,可以計(jì)算出第2個(gè)單元格屬于第一組第2個(gè)格(從第0格算起)。
第三步,制作“取值器”,0x1<<2, 得到00000100。
最后,通過(guò)"bytes[0]&取值器"判斷結(jié)果是大于0還是等于0, 就可以得到第2個(gè)單元格的值了。
let res = 0x00000111 & 0x00000100 ? 1 : 0
res的值為1, 也就是第2個(gè)單元格的值為1。是不是很好玩?
未完待續(xù)......
往期回顧
二進(jìn)制、八進(jìn)制、十進(jìn)制、十六進(jìn)制數(shù)據(jù)轉(zhuǎn)換
?