我是这样理解base64编码原理的

596 阅读8分钟

我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第1篇文章,点击查看活动详情

背景

最近在和同事聊base64编码的时候,瞬间对base64的编码原理又产生了兴趣,脑海中回想起几年前曾经对base64编码原理进行过一些研究,但似乎有点儿陌生了,所以决定重新查阅笔记进行“翻修”,回炉重造,温故一下。

首先了解下,什么是base64编码

百度百科给出的解释是,base64编码是网络上最常见的用于传输8bit字节代码的编码方式之一。8bit什么意思?bit又是什么单位?接下来先了解几个基本概念。

什么是bit?

bit就是我们常说的“位”,"位(bit)"是计算机中最小的数据单位,每一位的状态只能是0或1。

什么是字节?

"字节(Byte)"是存储空间的基本计量单位,8个二进制位构成1个"字节(Byte)",通常存储一个英文字母需要1个字节,存储一个汉字需要2个字节。

了解了"位(bit)"和"字节(Byte)"这2个基本概念,我们再来了解下为什么要用base64编码方式,base64编码有什么作用?

通常,在使用计算机网络的过程中,我们会进行一些数据的传输,例如发送邮件,上传图片等等。由于有些网络传送渠道并不支持所有的字节类型。比如,传统的邮件只支持可见字符的传送,像ASCII码的控制字符就不能通过邮件传送。这样就受到了很大的限制;再比如,图片二进制流的每个字节不可能全部都是可见字符,所以就无法传送。此时,最好的方法就是在不改变传统协议的情况下,开辟一种新的方案来支持二进制文件的传送。把不可见字符用可见字符来表示。而Base64编码就是一种基于可见字符来表示二进制数据的方法。

疑问:什么是不可见字符?不可见字符会显示吗?

解答:不可见字符其实并不是不显示,只是这些字符在屏幕上显示不出来,比如:换行符(\n)、回车(\r)、退格(\b)等字符。

base64是如何进行编码的呢?

base64拥有自己的一套编码对照表,这套表中只有A—Z,a—z,0—9,+,/ 这64个可见字符。在进行base64进行编码时,可以将ASCII字符串或者是二进制字符用这64个可见字符进行表示出来。这64个可见字符只需要6个bit位就可以全部表示出来,事实上,一个base64字符仍然是采用8个bit位来表示的,只是将多出来的两个bit位用0来补充,有效部分只有右边的6个bit位,左边两个永远是0。base64的编码规则是将3个8位字节(3×8=24位)编码成4个6位的字节(4×6=24位),如果在进行编码的时候,拆分后的结果不够6位的用0来补上相应的位置,然后在每个6位字节的前面,补充两个0,形成4个8位字节的形式。

由于base64是将3个字节转变为了4个字节,因此,编码后的代码量(以字节为单位)比编码前的代码量多了约1/3。如果编码前的代码量正好是3的整数倍,那么编码后的代码量将恰好多出1/3;但如果编码前的代码量不是3的倍数,那么,此时需要在空出的结果位置用“=”来补位。总之要保证最后编码出来得字节数是4的倍数(此处不太好理解,不要着急,可结合文章后面的实际案例进行理解)。

疑问:为什么要保证最后编码出来的字节数是4的倍数?

解答:因为base64编码时,是将3个字节转变为4个字节,最终得到的字节数必然是4的倍数

base64编码的一个主要目的,是把任何字符都用“可视”字符表现出来。先把字符串拆开,成为8位二进制(前两位补零)的形式,这样每个字符的范围都在0-63之间了。再用base64的编码表,把取值范围在0-63的字符变成“可视”字符。如果不加零或只加一个零,那么取值范围就会是0-255或0-127,base64的编码表就要重新规定了。

疑问:为什么取值范围是0 ~ 63?

解答:可以回顾一下二进制转换10进制的方法:最小的二进制:00000000转换为10进制的结果是;最大的二进制:00111111转换为10进制的结果是63;

疑问:为什么取值范围限制在0 ~ 63,而不是0 ~ 255或者0 ~ 127

猜测:估计可见字符有限,没有那么多的可见字符或者是Base64编码的规则、约定

下图是Base64编码对照表,数值代表字符的索引,这个是标准Base64协议规定的,不能更改。

image.png

案例

案例一:原始代码量是3的倍数

  1. 原字符:MNG
  2. 对应ASCII码分别是:M: 77, N: 78, G: 71
  3. 转换成对应的二进制分别是:M: 01001101, N: 01001110, G: 01000111
  4. 将对应的二进制好合并在一起:010011010100111001000111
  5. 每6位分为一组进行拆分:010011,010100,111001,000111
  6. 将已分组的6位二进制分别补零成8位:00010011,00010100,00111001,00000111
  7. 将二进制分别转换为对应的10进制:19, 20, 57, 7
  8. 根据base64编码表查询对应字符分别是:T, U, 5, H
  9. 原字符“MNG”长度是3,是3的倍数,无需补位
  10. 最终得到的结果是:TU5H

案例二:原始代码量不是3的倍数

  1. 原字符:MN
  2. 对应ASCII码分别是:M: 77, N: 78
  3. 转换成对应的二进制分别是:M: 01001101, N: 01001110
  4. 将对应的二进制好合并在一起:0100110101001110
  5. 每6位分为一组进行拆分:010011,010100,1110
  6. 最后的“11”不够6位用0来补上:010011,010100,111000
  7. 将已分组的6位二进制分别补零成8位:00010011,00010100,00111000
  8. 将二进制分别转换为对应的10进制:19, 20, 56
  9. 根据base64编码表查询对应字符分别是:T, U, 4
  10. 原字符“MNGC”长度是2,不是3的倍数,须补1位“=”
  11. 最终得到的结果是:TU4=

案例三:原始代码量不是3的倍数

  1. 原字符:MNGC
  2. 对应ASCII码分别是:M: 77, N: 78, G: 71, C: 67
  3. 转换成对应的二进制分别是:M: 01001101, N: 01001110, G: 01000111, C: 1000011
  4. 将对应的二进制好合并在一起:01001101010011100100011101000011
  5. 每6位分为一组进行拆分:010011,010100,111001,000111, 010000, 11
  6. 最后的“11”不够6位用0来补上:010011,010100,111001,000111, 010000, 110000
  7. 将已分组的6位二进制分别补零成8位:00010011,00010100,00111001,00000111, 00010000, 00110000
  8. 将二进制分别转换为对应的10进制:19, 20, 57, 7, 16, 48
  9. 根据base64编码表查询对应字符分别是:T, U, 5, H, Q, w
  10. 原字符“MNGC”长度是4,不是3的倍数,须补2位“=”
  11. 最终得到的结果是:TU5HQw==

最后

非常建议大家结合最后的的实际案例进行阅读,方便理解何时进行补位“=”,需要补1位还是2位。本篇笔记是结合了几年前写的总结,进行了重新改写,欢迎各位朋友提出宝贵意见,如果有不恰当之处,还望各位批评指正。