都知道Base64,Base32你能實(shí)現(xiàn)嗎?

很長(zhǎng)時(shí)間沒(méi)有更新個(gè)人博客了,因?yàn)榍耙欢螘r(shí)間在換工作,入職了一家新的公司,剛開(kāi)始需要適應(yīng)一下新公司的節(jié)奏,開(kāi)始階段也比較忙。新公司還是有一定的技術(shù)氣氛的,每周都會(huì)有技術(shù)分享,而且還會(huì)給大家留一些思考題,這次的思考題就是讓我們回去實(shí)現(xiàn)一個(gè)Base32的編碼和解碼。

這可怎么辦?Base64也就知道個(gè)大概,Base32怎么實(shí)現(xiàn)呀?回去一頓惡補(bǔ),查資料,看Base64源碼,最后終于將Base32實(shí)現(xiàn)了。

Base64是干什么用的

要寫(xiě)B(tài)ase32,就要先理解Base64,那么Base64是干什么用的呢?為什么要有Base64呢?這個(gè)是根本原因,把Base64產(chǎn)生的過(guò)程搞清楚了,那么Base32,我們就可以依葫蘆畫(huà)瓢了。

我們知道在計(jì)算機(jī)中,數(shù)據(jù)的單位是字節(jié)byte,它是由8位2進(jìn)制組成的,總共可以有256個(gè)不同的數(shù)。那么這些二進(jìn)制的數(shù)據(jù)要怎么進(jìn)行傳輸呢?我們要將其轉(zhuǎn)化為ASCII字符,ASCII字符中包含了33個(gè)控制字符(不可見(jiàn))和95個(gè)可見(jiàn)字符,我們?nèi)绻軐⑦@些二進(jìn)制的數(shù)據(jù)轉(zhuǎn)化成這95個(gè)可見(jiàn)字符,就可以正常傳輸了。于是,我們從95個(gè)字符中,挑選了64個(gè),將2進(jìn)制的數(shù)據(jù)轉(zhuǎn)化為這個(gè)64個(gè)可見(jiàn)字符,這樣就可以正常的傳輸了,這就是Base64的由來(lái)。那這64個(gè)字符是什么呢?


image-20210120200322327.png

這就是Base64的那64個(gè)字符。那么如果我們要實(shí)現(xiàn)Base32呢?對(duì)了,我們要挑選出32個(gè)可見(jiàn)字符,具體如下:

private static final char[] toBase32 = {
  'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
  'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
  '0', '1', '2', '3', '4', '5'
};

我們挑選了大寫(xiě)的A-Z,再加上0-5,一共32個(gè)可見(jiàn)字符。

Base32是什么規(guī)則

好了,32個(gè)可見(jiàn)字符已經(jīng)選好了,接下來(lái)就是將2進(jìn)制轉(zhuǎn)化成這32個(gè)字符的過(guò)程。我們先來(lái)看一下Base64是一個(gè)什么樣的轉(zhuǎn)化過(guò)程,我們一個(gè)字節(jié)是8位,而64是2的6次方,也即是一個(gè)字節(jié)(8位)的數(shù)據(jù),我們要截取其中的6位進(jìn)行編碼,取到其可見(jiàn)字符。那么剩余的2位數(shù)怎么辦呢?它將和下一個(gè)自己的前4位組成一個(gè)6位的數(shù)據(jù)進(jìn)行編碼。那么我們需要多少字節(jié)才能得到一個(gè)完整的不丟位的編碼呢?我們要取6和8的最小公倍數(shù),也就是24,24位恰好是3個(gè)字節(jié),如果取6位進(jìn)行編碼,則可以取到4個(gè)編碼。我們看看下面的圖就可以更好地理解了,


image-20210120202758851.png
  • M,a,n對(duì)應(yīng)的ASCII碼分別是77,97,110。
  • 對(duì)應(yīng)的二進(jìn)制是01001101,01100001,01101110。
  • 然后我們按照6位截取,恰好能夠截取4個(gè)編碼,對(duì)應(yīng)的6位二進(jìn)制分別為:010011,010110,000101,101110。
  • 對(duì)應(yīng)的64位編碼為:T,W,F(xiàn),u。

同理,如果我們要實(shí)現(xiàn)Base32怎么辦呢?32是2的5次方,那么我們?cè)龠M(jìn)行2進(jìn)制截位時(shí),要一次截取5位。那么一個(gè)字節(jié)8位,截取了5位,剩下的3位怎么辦?同理和下一個(gè)字節(jié)的前2位組成一個(gè)新的5位。那么多少個(gè)字節(jié)按照5位截取才能不丟位呢?我們要取5和8的最小公倍數(shù),40位,按照5位截取,正好得到8個(gè)編碼。40位,正好5個(gè)字節(jié),所以我們要5個(gè)字節(jié)分為一組,進(jìn)行Base32的編碼。如下圖:


image-20210121104139278.png

對(duì)比前面的Base64,Base32就是按照5位去截取,然后去編碼表中找到對(duì)應(yīng)的字符。好了,原理我們明白了,下面進(jìn)入程序階段。

寫(xiě)程序階段

原理明白了,程序怎么寫(xiě)呢?這也就是程序猿的價(jià)值所在,把現(xiàn)實(shí)中的規(guī)則、功能、邏輯用程序把它實(shí)現(xiàn)。但是實(shí)現(xiàn)Base32也是比較難的,不過(guò)有先人給我們留下了Base64,我們參照Base64去實(shí)現(xiàn)Base32就容易多了。

Base32編碼

首先,我們要根據(jù)輸入字節(jié)的長(zhǎng)度,確定返回字節(jié)的長(zhǎng)度,以上面為例,輸入字節(jié)的長(zhǎng)度是5,那么Base32轉(zhuǎn)碼后的字節(jié)長(zhǎng)度就是8。那么如果輸入字節(jié)的長(zhǎng)度是1,返回結(jié)果的字節(jié)長(zhǎng)度是多少呢?這就需要補(bǔ)位了,也就是說(shuō)輸入字節(jié)的長(zhǎng)度不是5的倍數(shù),我們要進(jìn)行補(bǔ)位,將其長(zhǎng)度補(bǔ)成5的倍數(shù),這樣編碼以后,返回字節(jié)的長(zhǎng)度就是8的倍數(shù)。這樣做,我們不會(huì)丟失信息,比如,我們只輸入了一個(gè)字節(jié),是8位,編碼時(shí),截取了前5位,那么剩下的后3位怎么辦?不能舍棄吧,我們要在其后面補(bǔ)足40位,補(bǔ)位用0去補(bǔ),前面截取有剩余的位數(shù)再加上后面補(bǔ)位的0,湊成5位,再去編碼。其余的,全是0的5位二進(jìn)制,我們編碼成“=”,這個(gè)和Base64是一樣的。

好了,我們先來(lái)看看編碼后返回字節(jié)的長(zhǎng)度怎么計(jì)算。

//返回結(jié)果的數(shù)組長(zhǎng)度
int rLength = 8 * ((src.length + 4) / 5);
//返回結(jié)果
byte[] result = new byte[rLength];
  • 其中src是輸入的字節(jié)數(shù)組;
  • 返回長(zhǎng)度的公式我們要仔細(xì)看一下,對(duì)5取整,再乘以8,這是一個(gè)最基本的操作,我們用上面的例子套一下,輸入字節(jié)的長(zhǎng)度是5個(gè)字節(jié),8*(5/5) = 8,需要返回8個(gè)字節(jié)。我們?cè)賮?lái)看看加4的作用,比如我們輸入的是1個(gè)字節(jié),那么返回幾個(gè)字節(jié)呢?按照前面的要求,如果二進(jìn)制長(zhǎng)度不滿40位,要補(bǔ)滿40位,也就是輸入字節(jié)的長(zhǎng)度要補(bǔ)滿成5的整數(shù)倍。這里先加4再對(duì)5取整,就可以補(bǔ)位后可以進(jìn)行完整編碼的個(gè)數(shù),然后再乘以8,得到返回的字節(jié)數(shù)。大家可以隨便想幾個(gè)例子,驗(yàn)證一下結(jié)果對(duì)不對(duì)。
  • 然后我們定義返回結(jié)果的數(shù)組。

返回結(jié)果的數(shù)組長(zhǎng)度已經(jīng)確定了,接下來(lái)我們做什么呢?當(dāng)然是編碼的工作了,這里我們分為兩個(gè)步驟:

  1. 先處理可以正常進(jìn)行編碼的那些字節(jié),也就是滿足5的倍數(shù)的那些字節(jié),這些字節(jié)可以進(jìn)行5字節(jié)到8字節(jié)轉(zhuǎn)換的,不需要進(jìn)行補(bǔ)位。
  2. 然后處理最后幾位,這些是需要補(bǔ)位的,將其補(bǔ)成5個(gè)字節(jié)。

編碼的步驟已經(jīng)確定了,下面要確定可以正常編碼的字節(jié)長(zhǎng)度,以及需要補(bǔ)位的長(zhǎng)度,如下:

//正常轉(zhuǎn)換的長(zhǎng)度
int normalLength = src.length / 5 * 5;
//補(bǔ)位長(zhǎng)度
int fillLength = (5 - (src.length % 5)) % 5;

又是兩個(gè)計(jì)算公式,我們分別看一下:

  1. 可以正常編碼的字節(jié)長(zhǎng)度,對(duì)5取整,再乘以5,過(guò)濾掉最后不滿足5的倍數(shù)的字節(jié),這些過(guò)濾掉的字節(jié)需要補(bǔ)位,滿足5個(gè)字節(jié);
  2. 這一步就是計(jì)算最后需要補(bǔ)幾位才能滿足5的倍數(shù),最后可以得到需要補(bǔ)位的長(zhǎng)度,如果輸入字節(jié)的長(zhǎng)度恰好是5的倍數(shù),不需要補(bǔ)位,則計(jì)算的結(jié)果是0,大家可以驗(yàn)證一下這兩個(gè)公式。

接下來(lái),我們處理一下可以正常編碼的字節(jié),如下:

//輸入字節(jié)下標(biāo)
int srcPos = 0;
//返回結(jié)果下標(biāo)
int resultPos = 0;
while (srcPos < normalLength) {
  long bits = ((long)(src[srcPos++] & 0xff)) << 32 |
    (src[srcPos++] & 0xff) << 24 |
    (src[srcPos++] & 0xff) << 16 |
    (src[srcPos++] & 0xff) << 8  |
    (src[srcPos++] & 0xff);

  result[resultPos++] = (byte) toBase32[(int)((bits >> 35) & 0x1f)];
  result[resultPos++] = (byte) toBase32[(int)((bits >> 30) & 0x1f)];
  result[resultPos++] = (byte) toBase32[(int)((bits >> 25) & 0x1f)];
  result[resultPos++] = (byte) toBase32[(int)((bits >> 20) & 0x1f)];
  result[resultPos++] = (byte) toBase32[(int)((bits >> 15) & 0x1f)];
  result[resultPos++] = (byte) toBase32[(int)((bits >> 10) & 0x1f)];
  result[resultPos++] = (byte) toBase32[(int)((bits >> 5) & 0x1f)];
  result[resultPos++] = (byte) toBase32[(int)(bits & 0x1f)];

}
  1. 我們先定義輸入字節(jié)的下標(biāo)和返回結(jié)果的下標(biāo),用作取值與賦值;
  2. 再寫(xiě)個(gè)while循環(huán),只要輸入的字節(jié)下標(biāo)在正常轉(zhuǎn)換的范圍內(nèi),就可以正常的編碼;
  3. 接下來(lái)看看while循環(huán)的處理細(xì)節(jié),我們先要將5個(gè)字節(jié)拼成一個(gè)40位的二進(jìn)制,在程序中,我們通過(guò)位移運(yùn)算和 | 或運(yùn)算得到一個(gè)long型的數(shù)字,當(dāng)然它的二進(jìn)制就是我們用5個(gè)字節(jié)拼成的。
  4. 這里有個(gè)坑要和大家說(shuō)明一下,我們第一個(gè)字節(jié)位移的時(shí)候用long轉(zhuǎn)型了,為什么?因?yàn)閕nt型在Java中占4個(gè)字節(jié),32位,我們左移32位后,它會(huì)回到最右側(cè)的位置。而long占64位,我們左移32位是不會(huì)循環(huán)的。這一點(diǎn)大家要格外注意。
  5. 接下來(lái)就是將這40位的二進(jìn)制進(jìn)行分拆,同樣通過(guò)位移操作,每次從左側(cè)截取5位,我們分別向右移動(dòng)35、30、25、20、15、10、5、0,然后將其和0x1f進(jìn)行與操作,0x1f是一個(gè)16進(jìn)制的數(shù),其二進(jìn)制是0001 1111,對(duì)了,就是5個(gè)1,移位后和0x1f進(jìn)行與操作,只留取最右側(cè)的5位二進(jìn)制,并計(jì)算其數(shù)值,然后從32位編碼表中找到對(duì)應(yīng)的字符。

可以正常編碼的部分就正常結(jié)束了,大家要多多理解位移符號(hào)的運(yùn)用。接下來(lái),我們?cè)倏纯唇Y(jié)尾字節(jié)的處理。先上代碼:

if (fillLength > 0) {
  switch (fillLength) {
    case 1:
      int normalBits1 = (src[srcPos] & 0xff) << 24 |
        (src[srcPos+1] & 0xff) << 16 |
        (src[srcPos+2] & 0xff) << 8  |
        (src[srcPos+3] & 0xff);
      result[resultPos++] = (byte) toBase32[(normalBits1 >> 27) & 0x1f];
      result[resultPos++] = (byte) toBase32[(normalBits1 >> 22) & 0x1f];
      result[resultPos++] = (byte) toBase32[(normalBits1 >> 17) & 0x1f];
      result[resultPos++] = (byte) toBase32[(normalBits1 >> 12) & 0x1f];
      result[resultPos++] = (byte) toBase32[(normalBits1 >> 7) & 0x1f];
      result[resultPos++] = (byte) toBase32[(normalBits1 >> 2) & 0x1f];
      result[resultPos++] = (byte) toBase32[(normalBits1 << 3) & 0x1f];
      result[resultPos++] = '=';
      break;
    case 2:
      int normalBits2 = (src[srcPos] & 0xff) << 16 |
        (src[srcPos+1] & 0xff) << 8 |
        (src[srcPos+2] & 0xff);
      result[resultPos++] = (byte) toBase32[(normalBits2 >> 19) & 0x1f];
      result[resultPos++] = (byte) toBase32[(normalBits2 >> 14) & 0x1f];
      result[resultPos++] = (byte) toBase32[(normalBits2 >> 9) & 0x1f];
      result[resultPos++] = (byte) toBase32[(normalBits2 >> 4) & 0x1f];
      result[resultPos++] = (byte) toBase32[(normalBits2 << 1) & 0x1f];
      result[resultPos++] = '=';
      result[resultPos++] = '=';
      result[resultPos++] = '=';
      break;
    case 3:
      int normalBits3 = (src[srcPos] & 0xff) << 8 |
        (src[srcPos+1] & 0xff);
      result[resultPos++] = (byte) toBase32[(normalBits3 >> 11) & 0x1f];
      result[resultPos++] = (byte) toBase32[(normalBits3 >> 6) & 0x1f];
      result[resultPos++] = (byte) toBase32[(normalBits3 >> 1) & 0x1f];
      result[resultPos++] = (byte) toBase32[(normalBits3 << 4) & 0x1f];
      result[resultPos++] = '=';
      result[resultPos++] = '=';
      result[resultPos++] = '=';
      result[resultPos++] = '=';
      break;
    case 4:
      int normalBits4 = (src[srcPos] & 0xff) ;
      result[resultPos++] = (byte) toBase32[(normalBits4 >> 3) & 0x1f];
      result[resultPos++] = (byte) toBase32[(normalBits4 << 2) & 0x1f];
      result[resultPos++] = '=';
      result[resultPos++] = '=';
      result[resultPos++] = '=';
      result[resultPos++] = '=';
      result[resultPos++] = '=';
      result[resultPos++] = '=';
      break;
  }
}
  1. fillLength就是需要補(bǔ)位的位數(shù),如果等于0,我們就不需要補(bǔ)位了。大于0就需要進(jìn)行補(bǔ)位。
  2. 需要補(bǔ)位的情況,我們分為4種,分別為:補(bǔ)1位、補(bǔ)2位、補(bǔ)3位和補(bǔ)4位。
  3. 我嗯先看看補(bǔ)1位的情況,需要補(bǔ)1位,說(shuō)明之前剩下4個(gè)字節(jié),我們先將這4個(gè)字節(jié)拼起來(lái),那么第一個(gè)字節(jié)要向左移動(dòng)24位,這個(gè)和正常情況下第一個(gè)字節(jié)向左移動(dòng)的位數(shù)是不一樣的。剩余的字節(jié)分別向左移動(dòng)相應(yīng)的位數(shù),大家可以參照程序計(jì)算一下。
  4. 然后將得到的32位二進(jìn)制數(shù),從最高位每次截取5位,每次向右移動(dòng)位數(shù)分別為27、22、17、12、7、2,注意,最后剩下2位,不足5位,我們要向左移動(dòng)3位。移位后要和0x1f進(jìn)行與操作,這個(gè)作用和前面是一樣的,這里不贅述了。然后將得到的數(shù)字在32位編碼表中,去除對(duì)應(yīng)的字符。
  5. 剩下的位數(shù)我們統(tǒng)一使用=進(jìn)行補(bǔ)位。
  6. 其他的需要補(bǔ)1位、補(bǔ)2位和補(bǔ)3位的情況,我們重復(fù)步驟3-步驟5,里邊具體的移動(dòng)位數(shù)有所區(qū)別,需要大家仔細(xì)計(jì)算。

整個(gè)的編碼過(guò)程到這里就結(jié)束了,我們將result數(shù)組返回即可。

總結(jié)

到這里,Base32的編碼就實(shí)現(xiàn)了,大家可以運(yùn)行一下,這里就不演示了。整個(gè)的實(shí)現(xiàn)過(guò)程大家感覺(jué)怎么樣,我們總結(jié)一下,

  1. 原理,不知道其原理,我們就沒(méi)有辦法寫(xiě)程序。
  2. 定義32位字符編碼表,大家可以根據(jù)個(gè)人喜好進(jìn)行定義,沒(méi)有標(biāo)準(zhǔn),只要是可見(jiàn)字符就可以。
  3. 寫(xiě)程序時(shí),要注意正常位數(shù)的計(jì)算,補(bǔ)位位數(shù)的計(jì)算,以及左移右移,都是需要大家仔細(xì)思考的。

好了,Base32編碼的過(guò)程就結(jié)束了,還缺少解碼的過(guò)程,我們有時(shí)間再補(bǔ)上吧~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容