认识Base64编码

361 阅读4分钟

摘要

问题从一道算法题开始,要求是实现一个Base32的编码和解码。既然是编码,那无非就是操作二进制,以一定的规则转为一些常见的字符。解码就是逆规则。借此机会我查阅了base64相关的知识。下面主要是介绍下base64的编码规则,以及我们手动实现一下编码和解码的函数。

base64编码规则

为什么叫 base64 编码? Base64(基底64)是一种基于64个可打印字符来表示二进制数据的表示方法。由于log264=6,log_264=6所以每6个比特为一个单元。我们知道一个字节是8个比特,这里我们找6和8的最小公倍数,也就是24。也就是说,我们原本3个字节的数据,我们使用4个字符来标识。我们还需要64个字符,目前使用的是 A-Z、a-z、0-9一共62个外加+和/俩个字符,一共64个字符表示。(再加上作为垫字的"=",实际上是65个字符)

下面是字符对照表

base64编码表

详细编码步骤

这里我们也用最常用的 Man来解释

  • 转化为ASCII:Man的ASCII为 77、97、110
  • 二进制:010011010110000101101110,一共是24位。
  • 每6个位为一组,刚好是4组。010011010110000101101110
  • 上边那4组分别表示的值是:19、22、5、46。
  • 我们从字符对照表中找到对应的字符分别是:T、W、F、u。
  • 得到最终的结果:TWFu

如果我们的字节数不是6的倍数怎么办? 当然是补 0 了,难不成你还省略啊。 如果字节数小于3怎么办?我们在后边补充一个=号,也就是10000

代码实现

看了上边的解释,是不是了解了编码的过程。下面我们就手动实现一下编码解码的方法:base64_encodebase64_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个字符表,这个我们可以随便定义一下。
  • log232=5log_232=5所以我们每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,得到我们希望得到的码表,然后对其进行拆分,单位长度就是我们的单位编码长度。
  • 最后我们通过我们定义的编码表进行转换,得到我们需要的编码。

查阅资料