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

很長時間沒有更新個人博客了母债,因為前一段時間在換工作午磁,入職了一家新的公司,剛開始需要適應(yīng)一下新公司的節(jié)奏毡们,開始階段也比較忙迅皇。新公司還是有一定的技術(shù)氣氛的,每周都會有技術(shù)分享衙熔,而且還會給大家留一些思考題登颓,這次的思考題就是讓我們回去實現(xiàn)一個Base32的編碼和解碼。

這可怎么辦红氯?Base64也就知道個大概框咙,Base32怎么實現(xiàn)呀?回去一頓惡補痢甘,查資料喇嘱,看Base64源碼,最后終于將Base32實現(xiàn)了塞栅。

Base64是干什么用的

要寫B(tài)ase32者铜,就要先理解Base64,那么Base64是干什么用的呢放椰?為什么要有Base64呢作烟?這個是根本原因,把Base64產(chǎn)生的過程搞清楚了砾医,那么Base32拿撩,我們就可以依葫蘆畫瓢了。

我們知道在計算機中藻烤,數(shù)據(jù)的單位是字節(jié)byte绷雏,它是由8位2進制組成的,總共可以有256個不同的數(shù)怖亭。那么這些二進制的數(shù)據(jù)要怎么進行傳輸呢涎显?我們要將其轉(zhuǎn)化為ASCII字符,ASCII字符中包含了33個控制字符(不可見)和95個可見字符兴猩,我們?nèi)绻軐⑦@些二進制的數(shù)據(jù)轉(zhuǎn)化成這95個可見字符期吓,就可以正常傳輸了。于是,我們從95個字符中讨勤,挑選了64個箭跳,將2進制的數(shù)據(jù)轉(zhuǎn)化為這個64個可見字符,這樣就可以正常的傳輸了潭千,這就是Base64的由來谱姓。那這64個字符是什么呢?


image-20210120200322327.png

這就是Base64的那64個字符刨晴。那么如果我們要實現(xiàn)Base32呢屉来?對了,我們要挑選出32個可見字符狈癞,具體如下:

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'
};

我們挑選了大寫的A-Z茄靠,再加上0-5,一共32個可見字符蝶桶。

Base32是什么規(guī)則

好了慨绳,32個可見字符已經(jīng)選好了,接下來就是將2進制轉(zhuǎn)化成這32個字符的過程真竖。我們先來看一下Base64是一個什么樣的轉(zhuǎn)化過程脐雪,我們一個字節(jié)是8位,而64是2的6次方恢共,也即是一個字節(jié)(8位)的數(shù)據(jù)喂江,我們要截取其中的6位進行編碼,取到其可見字符旁振。那么剩余的2位數(shù)怎么辦呢获询?它將和下一個自己的前4位組成一個6位的數(shù)據(jù)進行編碼。那么我們需要多少字節(jié)才能得到一個完整的不丟位的編碼呢拐袜?我們要取6和8的最小公倍數(shù)吉嚣,也就是24,24位恰好是3個字節(jié)蹬铺,如果取6位進行編碼尝哆,則可以取到4個編碼。我們看看下面的圖就可以更好地理解了甜攀,


image-20210120202758851.png
  • M秋泄,a,n對應(yīng)的ASCII碼分別是77规阀,97恒序,110。
  • 對應(yīng)的二進制是01001101谁撼,01100001歧胁,01101110。
  • 然后我們按照6位截取,恰好能夠截取4個編碼喊巍,對應(yīng)的6位二進制分別為:010011屠缭,010110,000101崭参,101110呵曹。
  • 對應(yīng)的64位編碼為:T,W何暮,F(xiàn)逢并,u。

同理郭卫,如果我們要實現(xiàn)Base32怎么辦呢?32是2的5次方背稼,那么我們再進行2進制截位時贰军,要一次截取5位。那么一個字節(jié)8位蟹肘,截取了5位词疼,剩下的3位怎么辦?同理和下一個字節(jié)的前2位組成一個新的5位帘腹。那么多少個字節(jié)按照5位截取才能不丟位呢贰盗?我們要取5和8的最小公倍數(shù),40位阳欲,按照5位截取舵盈,正好得到8個編碼。40位球化,正好5個字節(jié)秽晚,所以我們要5個字節(jié)分為一組,進行Base32的編碼筒愚。如下圖:


image-20210121104139278.png

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

寫程序階段

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

Base32編碼

首先吏恭,我們要根據(jù)輸入字節(jié)的長度拗小,確定返回字節(jié)的長度,以上面為例樱哼,輸入字節(jié)的長度是5哀九,那么Base32轉(zhuǎn)碼后的字節(jié)長度就是8。那么如果輸入字節(jié)的長度是1搅幅,返回結(jié)果的字節(jié)長度是多少呢阅束?這就需要補位了,也就是說輸入字節(jié)的長度不是5的倍數(shù)茄唐,我們要進行補位息裸,將其長度補成5的倍數(shù),這樣編碼以后沪编,返回字節(jié)的長度就是8的倍數(shù)呼盆。這樣做,我們不會丟失信息蚁廓,比如访圃,我們只輸入了一個字節(jié),是8位相嵌,編碼時腿时,截取了前5位,那么剩下的后3位怎么辦饭宾?不能舍棄吧圈匆,我們要在其后面補足40位,補位用0去補捏雌,前面截取有剩余的位數(shù)再加上后面補位的0跃赚,湊成5位,再去編碼性湿。其余的纬傲,全是0的5位二進制,我們編碼成“=”肤频,這個和Base64是一樣的叹括。

好了,我們先來看看編碼后返回字節(jié)的長度怎么計算宵荒。

//返回結(jié)果的數(shù)組長度
int rLength = 8 * ((src.length + 4) / 5);
//返回結(jié)果
byte[] result = new byte[rLength];
  • 其中src是輸入的字節(jié)數(shù)組汁雷;
  • 返回長度的公式我們要仔細看一下净嘀,對5取整,再乘以8侠讯,這是一個最基本的操作挖藏,我們用上面的例子套一下,輸入字節(jié)的長度是5個字節(jié)厢漩,8*(5/5) = 8膜眠,需要返回8個字節(jié)。我們再來看看加4的作用溜嗜,比如我們輸入的是1個字節(jié)宵膨,那么返回幾個字節(jié)呢?按照前面的要求炸宵,如果二進制長度不滿40位辟躏,要補滿40位,也就是輸入字節(jié)的長度要補滿成5的整數(shù)倍土全。這里先加4再對5取整捎琐,就可以補位后可以進行完整編碼的個數(shù),然后再乘以8涯曲,得到返回的字節(jié)數(shù)。大家可以隨便想幾個例子在塔,驗證一下結(jié)果對不對幻件。
  • 然后我們定義返回結(jié)果的數(shù)組。

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

  1. 先處理可以正常進行編碼的那些字節(jié)贺待,也就是滿足5的倍數(shù)的那些字節(jié)徽曲,這些字節(jié)可以進行5字節(jié)到8字節(jié)轉(zhuǎn)換的,不需要進行補位麸塞。
  2. 然后處理最后幾位秃臣,這些是需要補位的,將其補成5個字節(jié)哪工。

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

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

又是兩個計算公式稚虎,我們分別看一下:

  1. 可以正常編碼的字節(jié)長度,對5取整偎捎,再乘以5蠢终,過濾掉最后不滿足5的倍數(shù)的字節(jié)序攘,這些過濾掉的字節(jié)需要補位酒唉,滿足5個字節(jié)西潘;
  2. 這一步就是計算最后需要補幾位才能滿足5的倍數(shù),最后可以得到需要補位的長度艰垂,如果輸入字節(jié)的長度恰好是5的倍數(shù)兜喻,不需要補位梦染,則計算的結(jié)果是0,大家可以驗證一下這兩個公式朴皆。

接下來帕识,我們處理一下可以正常編碼的字節(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. 再寫個while循環(huán),只要輸入的字節(jié)下標(biāo)在正常轉(zhuǎn)換的范圍內(nèi)扒接,就可以正常的編碼伪货;
  3. 接下來看看while循環(huán)的處理細節(jié),我們先要將5個字節(jié)拼成一個40位的二進制钾怔,在程序中碱呼,我們通過位移運算和 | 或運算得到一個long型的數(shù)字,當(dāng)然它的二進制就是我們用5個字節(jié)拼成的宗侦。
  4. 這里有個坑要和大家說明一下愚臀,我們第一個字節(jié)位移的時候用long轉(zhuǎn)型了,為什么矾利?因為int型在Java中占4個字節(jié)姑裂,32位,我們左移32位后男旗,它會回到最右側(cè)的位置舶斧。而long占64位,我們左移32位是不會循環(huán)的察皇。這一點大家要格外注意茴厉。
  5. 接下來就是將這40位的二進制進行分拆,同樣通過位移操作什荣,每次從左側(cè)截取5位呀忧,我們分別向右移動35、30溃睹、25而账、20、15因篇、10泞辐、5笔横、0,然后將其和0x1f進行與操作咐吼,0x1f是一個16進制的數(shù)吹缔,其二進制是0001 1111,對了锯茄,就是5個1厢塘,移位后和0x1f進行與操作,只留取最右側(cè)的5位二進制肌幽,并計算其數(shù)值晚碾,然后從32位編碼表中找到對應(yīng)的字符。

可以正常編碼的部分就正常結(jié)束了喂急,大家要多多理解位移符號的運用格嘁。接下來,我們再看看結(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就是需要補位的位數(shù)糕簿,如果等于0,我們就不需要補位了狡孔。大于0就需要進行補位懂诗。
  2. 需要補位的情況,我們分為4種苗膝,分別為:補1位殃恒、補2位、補3位和補4位荚醒。
  3. 我嗯先看看補1位的情況芋类,需要補1位隆嗅,說明之前剩下4個字節(jié)界阁,我們先將這4個字節(jié)拼起來,那么第一個字節(jié)要向左移動24位胖喳,這個和正常情況下第一個字節(jié)向左移動的位數(shù)是不一樣的泡躯。剩余的字節(jié)分別向左移動相應(yīng)的位數(shù),大家可以參照程序計算一下丽焊。
  4. 然后將得到的32位二進制數(shù)较剃,從最高位每次截取5位,每次向右移動位數(shù)分別為27技健、22写穴、17、12雌贱、7啊送、2偿短,注意,最后剩下2位馋没,不足5位昔逗,我們要向左移動3位。移位后要和0x1f進行與操作篷朵,這個作用和前面是一樣的勾怒,這里不贅述了。然后將得到的數(shù)字在32位編碼表中声旺,去除對應(yīng)的字符笔链。
  5. 剩下的位數(shù)我們統(tǒng)一使用=進行補位。
  6. 其他的需要補1位艾少、補2位和補3位的情況卡乾,我們重復(fù)步驟3-步驟5,里邊具體的移動位數(shù)有所區(qū)別缚够,需要大家仔細計算幔妨。

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

總結(jié)

到這里误堡,Base32的編碼就實現(xiàn)了,大家可以運行一下雏吭,這里就不演示了锁施。整個的實現(xiàn)過程大家感覺怎么樣,我們總結(jié)一下杖们,

  1. 原理悉抵,不知道其原理,我們就沒有辦法寫程序摘完。
  2. 定義32位字符編碼表姥饰,大家可以根據(jù)個人喜好進行定義,沒有標(biāo)準(zhǔn)孝治,只要是可見字符就可以列粪。
  3. 寫程序時,要注意正常位數(shù)的計算谈飒,補位位數(shù)的計算岂座,以及左移右移,都是需要大家仔細思考的杭措。

好了费什,Base32編碼的過程就結(jié)束了,還缺少解碼的過程手素,我們有時間再補上吧~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末鸳址,一起剝皮案震驚了整個濱河市赘那,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌氯质,老刑警劉巖募舟,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異闻察,居然都是意外死亡拱礁,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進店門辕漂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來呢灶,“玉大人,你說我怎么就攤上這事钉嘹⊙炷耍” “怎么了?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵跋涣,是天一觀的道長缨睡。 經(jīng)常有香客問我,道長陈辱,這世上最難降的妖魔是什么奖年? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮沛贪,結(jié)果婚禮上陋守,老公的妹妹穿的比我還像新娘。我一直安慰自己利赋,他們只是感情好水评,可當(dāng)我...
    茶點故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著媚送,像睡著了一般中燥。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上季希,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天褪那,我揣著相機與錄音幽纷,去河邊找鬼式塌。 笑死,一個胖子當(dāng)著我的面吹牛友浸,可吹牛的內(nèi)容都是我干的峰尝。 我是一名探鬼主播,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼收恢,長吁一口氣:“原來是場噩夢啊……” “哼武学!你這毒婦竟也來了祭往?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤火窒,失蹤者是張志新(化名)和其女友劉穎硼补,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體熏矿,經(jīng)...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡已骇,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了票编。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片褪储。...
    茶點故事閱讀 40,030評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖慧域,靈堂內(nèi)的尸體忽然破棺而出鲤竹,到底是詐尸還是另有隱情,我是刑警寧澤昔榴,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布辛藻,位于F島的核電站,受9級特大地震影響互订,放射性物質(zhì)發(fā)生泄漏揩尸。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一屁奏、第九天 我趴在偏房一處隱蔽的房頂上張望岩榆。 院中可真熱鬧,春花似錦坟瓢、人聲如沸勇边。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽粒褒。三九已至,卻和暖如春诚镰,著一層夾襖步出監(jiān)牢的瞬間奕坟,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工清笨, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留月杉,地道東北人。 一個月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓抠艾,卻偏偏與公主長得像苛萎,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,976評論 2 355

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