摘要
问题从一道算法题开始,要求是实现一个Base32的编码和解码。既然是编码,那无非就是操作二进制,以一定的规则转为一些常见的字符。解码就是逆规则。借此机会我查阅了base64相关的知识。下面主要是介绍下base64的编码规则,以及我们手动实现一下编码和解码的函数。
base64编码规则
为什么叫 base64 编码? Base64(基底64)是一种基于64个可打印字符来表示二进制数据的表示方法。由于所以每6个比特为一个单元。我们知道一个字节是8个比特,这里我们找6和8的最小公倍数,也就是24。也就是说,我们原本3个字节的数据,我们使用4个字符来标识。我们还需要64个字符,目前使用的是 A-Z、a-z、0-9一共62个外加+和/俩个字符,一共64个字符表示。(再加上作为垫字的"=",实际上是65个字符)
下面是字符对照表
详细编码步骤
这里我们也用最常用的 Man来解释
- 转化为ASCII:
Man的ASCII为 77、97、110 - 二进制:
01001101、01100001、01101110,一共是24位。 - 每6个位为一组,刚好是4组。
010011、010110、000101、101110。 - 上边那4组分别表示的值是:19、22、5、46。
- 我们从字符对照表中找到对应的字符分别是:T、W、F、u。
- 得到最终的结果:
TWFu
如果我们的字节数不是6的倍数怎么办? 当然是补
0了,难不成你还省略啊。 如果字节数小于3怎么办?我们在后边补充一个=号,也就是10000
代码实现
看了上边的解释,是不是了解了编码的过程。下面我们就手动实现一下编码解码的方法:base64_encode、base64_decode。
这里我们先回想一下我们可能用到的几个方法,首先我们获取字符的ASCII码,使用方法是 charCodeAt。我们将数字转为二进制的方法是toString(2),我们将二进制转为数字的方法是parseInt(binaryString,2)。
定义一个map表
let map = {
'0': 'A',
'1': 'B',
'2':'C',
'3':'D',
'4':'E',
'5':'F',
'6':'G',
'7':'H',
'8':'I',
'9':'J',
'10':'K',
'11':'L',
'12':'M',
'13':'N',
'14':'O',
'15':'P',
'16':'Q',
'17':'R',
'18':'S',
'19':'T',
'20':'U',
'21':'V',
'22':'W',
'23':'X',
'24':'Y',
'25':'Z',
'26':'a',
'27':'b',
'28':'c',
'29':'d',
'30':'e',
'31':'f',
'32':'g',
'33':'h',
'34':'i',
'35':'j',
'36':'k',
'37':'l',
'38':'m',
'39':'n',
'40':'o',
'41':'p',
'42':'q',
'43':'r',
'44':'s',
'45':'t',
'46':'u',
'47':'v',
'48':'w',
'49':'x',
'50':'y',
'51':'z',
'52':'0',
'53':'1',
'54':'2',
'55':'3',
'56':'4',
'57':'5',
'58':'6',
'59':'7',
'60':'8',
'61':'9',
'62':'+',
'63':'-',
'64':'=',
}
function base64_encode() {
let i = 0,bstring='';
while(i < str.length) {
let c = str.charCodeAt(i);
let binary = '0'+c.toString(2);
bstring+=binary;
i++;
}
let append_str_len = (24 - (bstring.length % 24))%24;
bstring+='0'.repeat(append_str_len);
let j = 0,target='';
while(j < bstring.length) {
let bel = bstring.substr(j,6);
let el = parseInt(bel,2);
target+=map[el];
j+=6;
}
return target;
}
function vtoK(v){
for(let k in map) {
let _v = map[k];
if(_v == v) {
return k;
}
}
}
function base64_decode(str) {
let i = 0,bstring='';
while(i < str.length) {
let c = vtoK(str.charAt(i));
let binary = (+c).toString(2).padStart(6,'0');
bstring+=binary;
i++;
}
let j = 0,target='';
while(j < bstring.length) {
let bel = bstring.substr(j,8);
let el = parseInt(bel,2);
target+=String.fromCharCode(el);
j+=8;
}
return target;
}
猜想 Base32的逻辑
- 我们需要32个字符表,这个我们可以随便定义一下。
- 所以我们每5个位为一个组。
- 5 和 8 的最小公倍数是 40,所以我们必须凑够 40 个位,也就是5个字节。如果凑不够了就补0。
代码实现
function base32_encode() {
let i = 0,bstring='';
while(i < str.length) {
let c = str.charCodeAt(i);
let binary = '0'+c.toString(2);
bstring+=binary;
i++;
}
let append_str_len = (40 - (bstring.length % 40))%40;
bstring+='0'.repeat(append_str_len);
let j = 0,target='';
while(j < bstring.length) {
let bel = bstring.substr(j,5);
let el = parseInt(bel,2);
target+=map[el];
j+=5;
}
return target;
}
function base32_decode() {
let i = 0,bstring='';
while(i < str.length) {
let c = vtoK(str.charAt(i));
let binary = (+c).toString(2).padStart(5,'0');
bstring+=binary;
i++;
}
let j = 0,target='';
while(j < bstring.length) {
let bel = bstring.substr(j,8);
let el = parseInt(bel,2);
target+=String.fromCharCode(el);
j+=8;
}
return target;
}
拓展-如何设计编码方式
- 首先我们肯定得是有一个编码表,而且数量必须得是2的倍数,并且是比8大,最好是比256小。(16、32、64、128...)。
- 同理我们获取了要编码的字符串的二进制,对二进制进行补0,得到我们希望得到的码表,然后对其进行拆分,单位长度就是我们的单位编码长度。
- 最后我们通过我们定义的编码表进行转换,得到我们需要的编码。