其他

Base64介绍

前言


我们知道计算机中的文件可以分为两种,一种是人肉眼可读的文本类文件,一种是肉眼不可读的二进制文件。一般来说二进制文件如果用文本编辑器打开的话会显示乱码,并且二进制文件和文本文件的存储和传输方式是不一样的,那么有没有什么办法将二进制文件转换成为文本文件进行传输或者存储呢?答案是肯定的。

这种编码方式就是我们今天要讲到的Base64编码

Base64是怎么诞生的?

互联网发展早起,电子邮件是最有效的应用。
而电子邮件的SMTP传输协议在早期,只能用于传送7位的ASCII码,而ASCII码就是基于英语设计的,对于非英语国家的文字等资源就无法发送。为了解决这个问题,后来有了通用互联网邮件扩充MIME,增加了邮件的主体结构,定义了非ASCII码的编码传输规则,这就是Base64。

Base64是一种将二进制编码格式转换为text编码的一种形式。我们知道二进制编码是0和1的形式,它的单位通常是一个字节,也就是8bits,每个bit表示的是0或者1。

而文本编码的格式有很多种,最早也就是最简单的编码格式就是ASCII编码,ASCII编码的全称是American Standard Code for Information Interchange,也就是美国信息交换标准代码,它主要表示的是常用的一些西欧字符。

ASCII的编码范围是0x00-0x7F,用十进制来表示就是0-127,总共128个字符,刚好是7bits表示的范围。

Base64就是从ASCII编码中挑选出64个字符和二进制一个字节8bits进行映射,这也就是Base64中64的含义。为什么要选择ASCII编码呢?这是因为ASCII编码是最早出现的编码形式,几乎所有的计算机应用都对其完全支持,不会出现数据传输过程中的内容转换,非常的安全。

当然Base64编码也有多种编码形式,比如在MIME中,Base64选择的是A-Z, a-z, 和 0-9 总共62个字符,再加上其他自选的两个字符组成了64个编码字符。

64个字符用二进制表示是6bits,而常用的二进制使用一个字节来表示,也就是8bits,那么问题来了,怎么将8bits的二进制用6bits的Base64字符来表示呢

很简单,我们只需要将3个8bits连接起来,变成24bits,这样就可以用4个Base64来表示了。

为什么必须对二进制进行转换呢?这是因为互联网中的某些传输协议只支持某些特定的字符集,如果是其他的字符集是不支持的。比如说常用的发送电子邮件的附件。因为SMTP协议最开始设计的时候是支持7 位 ASCII 字符,所以如果要传输文件的话,我们需要对文件进行编码之后再进行传输。

网络传送渠道并不支持所有的字节,例如传统的邮件只支持可见字符的传送,像ASCII码的控制字符就 不能通过邮件传送。这样用途就受到了很大的限制,比如图片二进制流的每个字节不可能全部是可见字符,所以就传送不了。
最好的方法就是在不改变传统协议的情况下,做一种扩展方案来支持二进制文件的传送。把不可打印的字符也能用可打印字符来表示,问题就解决了。Base64编码应运而生,Base64就是一种 基于64个可打印字符来表示二进制数据的表示方法。在网络上交换数据时,比如说从A地传到B地,往往要经过多个路由设备,由于不同的设备对字符的处理方式有一些不同,这样那些不可见字符就有可能被处理错误,这是不利于传输的。所以就先 把数据先做一个Base64编码,统统变成可见字符,这样出错的可能性就大降低了。

另外Base64的一种用法就是在HTML中将图片嵌入到网页中,从而实现图片的展示。

虽然Base64很好用,但是因为其只能使用6bits的字符映射集,所以会造成数据映射的损失,从而导致二进制文件编码过后文件体积变大的缺点。

base64在web上的应用


我们进行前端开发时,针对项目优化,常会提到一条:针对较小图片,合理使用Base64字符串替换内嵌,可以减少页面http请求。并且还会特别强调下,必须是小图片,大小不要超过多少KB,等等。

下面的这段字符串,应该是大家都很常见的。通过这种固定的格式,来表示一张图片,并被浏览器识别,可以完整的展示出图片:

// 这里展示的是一个svg格式的图片,当然我们还可以加载任何浏览器支持的格式的图片。
data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0c......

这段字符串就是基于Base64编码得来的,其中base64,后面那一长串的字符串,就是Base64编码字符串。

我不是来讲概念的,直接切入正题,图片的 base64 编码就是可以将一副图片数据编码成一串字符串,使用该字符串代替图像地址

这样做有什么意义呢?我们知道,我们所看到的网页上的每一个图片,都是需要消耗一个 http 请求下载而来的

没错,不管如何,图片的下载始终都要向服务器发出请求,要是图片的下载不用向服务器发出请求,而可以随着 HTML 的下载同时下载到本地那就太好了,而 base64 正好能解决这个问题。

那么图片的 base64 编码长什么样子呢?举个栗子。www.google.com 的首页搜索框右侧的搜索小图标使用的就是base64编码。我们可以看到:

//在css里的写法
#fkbx-spch, #fkbx-hspch {
  background: url(data:image/gif;base64,R0lGODlhHAAmAKIHAKqqqsvLy0hISObm5vf394uLiwAAAP///yH5B…EoqQqJKAIBaQOVKHAXr3t7txgBjboSvB8EpLoFZywOAo3LFE5lYs/QW9LT1TRk1V7S2xYJADs=) no-repeat center;
}


//在html代码img标签里的写法
<img src="data:image/gif;base64,R0lGODlhHAAmAKIHAKqqqsvLy0hISObm5vf394uLiwAAAP///yH5B…EoqQqJKAIBaQOVKHAXr3t7txgBjboSvB8EpLoFZywOAo3LFE5lYs/QW9LT1TRk1V7S2xYJADs=">


Base64编码方式


就是包括小写字母a-z、大写字母A-Z、数字0-9、符号”+”、”/”一共64个字符的字符集,(另加一个“=”,实际是65个字符)。任何符号都可以转换成这个字符集中的字符,这个转换过程就叫做base64编码。

Base64是基于64个可打印字符来表示二进制数据的编解码方式。
正因为可编解码,所以它主要的作用不在于安全性,而在于让内容能在各个网关间无错的传输。

百度百科中对Base64有一个很好的解释:“Base64是网络上最常见的用于传输8Bit字节码的编码方式之一,Base64就是一种基于64个可打印字符来表示二进制数据的方法”。

什么是“可打印字符”呢?为什么要用它来传输8Bit字节码呢?在回答这两个问题之前我们有必要来思考一下什么情况下需要使用到Base64?Base64一般用于在HTTP协议下传输二进制数据,由于HTTP协议是文本协议,所以在HTTP协议下传输二进制数据需要将二进制数据转换为字符数据。然而直接转换是不行的。因为网络传输只能传输可打印字符。什么是可打印字符?在ASCII码中规定,0~31、127这33个字符属于控制字符,32~126这95个字符属于可打印字符,也就是说网络传输只能传输这95个字符,不在这个范围内的字符无法传输。那么该怎么才能传输其他字符呢?其中一种方式就是使用Base64。

具体的编码方式:

  • 将每3个字节作为一组,3个字节一共24个二进制位
  • 将这24个二进制位分为4组,每个组有6个二进制位
  • 在每组的6个二进制位前面补两个00,扩展成32个二进制位,即四个字节
  • 每个字节对应的将是一个小于64的数字,即为字符编号
  • 再根据字符索引关系表,每个字符编号对应一个字符,就得到了Base64编码字符

Base64,就是使用64个可打印字符来表示二进制数据的方法。Base64的索引与对应字符的关系如下表所示:

也就是说,如果将索引转换为对应的二进制数据的话需要至多6个Bit。然而ASCII码需要8个Bit来表示,那么怎么使用6个Bit来表示8个Bit的数据呢?6个Bit当然不能存储8个Bit的数据,但是4*6个Bit可以存储3*8个Bit的数据啊!如下表所示:

可以看到“Son”通过Base64编码转换成了“U29u”。这是刚刚好的情况,3个ASCII字符刚好转换成对应的4个Base64字符。但是,当需要转换的字符数不是3的倍数的情况下该怎么办呢?Base64规定,当需要转换的字符不是3的倍数时,一律采用补0的方式凑足3的倍数,具体如下表所示:

每6个Bit为一组,第一组转换后为字符“U”,第二组末尾补4个0转换后为字符“w”。剩下的使用“=”替代。即字符“S”通过Base64编码后为“Uw==”。这就是Base64的编码过程。

上图中的字符串 'you',经过转换后,得到的编码为: 'eW91'

体积增大


我们可以看到,当3个字符进行Base64转换编码后,最后变成了4个字符。因为每个6比特位,都补了2个0,变成8比特位,对应1字节。这里正好多了三分之一,所以正常情况下,Base64编码的数据体积通常比原数据的体积大三分之一
这也是为什么我们在前面讲使用Base64编码优化图片时,需要强调是小图标,如果图片都使用该方式,则静态文件会增大很多,并不合适。

= 等号


3个英文字符,正好能转成4个Base64字符。那如果字符长度不是3的倍数,那应该使用什么样的规则呢?
其实也简单,我们在实际使用Base编码时,常会发现有第65个字符的存在,那就是 '=' 符号,这个等于号就是针对这种特殊情况的一种处理方式。
对于不足3个字节的地方,实际都会在后面补0,直到有24个二进制位为止。
但要注意的是,在计算字节数时,会直接使用总长度除以3,如果余数为1则会直接在最后补一个=,如果余数为2则补两个=
因此,转码后的字符串需要补的后缀等号,要么是1个,要么是2个,具体的可以见下图:

图中第二个,使用的是单独的字符 'd',是为了区分索引字符表里的索引0,这个时候,得到编码中,会存在一个索引0对应的A字符,而'='是直接补上2个。

非ASCII码字符


由于 Base64 仅可对 ASCII 字符进行编码,如果是中文字符等非ASCII码,就需要先将中文字符转换为ASCII字符后,再进行编码才行。

编解码方法


btoa 和 atob

w3c浏览器内置了window.btoa()window.atob() 来完成二进制数据或者ASCII字符到base64的转换(注意:该方法只能用于ASCII码成Base64,不适用中文等)

JavaScript提供了两个原生方法,用来处理Base64编码:btoa()atob()

  • btoa(): 将字符串或二进制值转换成Base64编码字符串。
    注意:btoa方法只能直接处理ASCII码的字符,对于非ASCII码的字符,则会报错。
  • atob(): 对base64 编码的字符串进行解码。
    注意:atob方法如果传入字符串参数不是有效的Base64编码(如非ASCII码字符),或者其长度不是4的倍数,会报错。
btoa('you') // 'eW91'atob('eW91') // 'you'

btoa('中') // Uncaught DOMException: The string to be encoded contains characters outside of the Latin1 range.atob('y') // Uncaught DOMException: The string to be decoded is not correctly encoded.

处理中文字符


由于btoa、atob 仅支持对ASCII字符编码,也就是单字节字符,而我们平时的中文都是 2-4 字节的字符。
因此,可以先将中文字符转为 utf-8 的编码,将utf-8编码当做字符,这样就可以对多个单字节字符进行编码。

对于中文可以使用这两个方法: encodeURIComponent()decodeURIComponent()

  • encodeURIComponent():将非ACSII码的字符进行utf-8编码
  • decodeURIComponent():解码使用

如下,编解码中文的方式:

window.btoa(encodeURIComponent('中国'))// 'JUU0JUI4JUFEJUU1JTlCJUJE'decodeURIComponent(window.atob('JUU0JUI4JUFEJUU1JTlCJUJE'))// '中国'

使用场景


  • html中的图片用base64表示

网页中能看到某些样式中的图片不是一个资源地址,而是base64编码的字符串,这么做有什么好处呢? 当然是减少了一次http的请求,但也并不是什么图片都适合用base64来处理,因为图片越大,转换的base64的字符串就越长,对带宽的要求更高了。

  • 邮件传输

早期电子邮件的收发只允许使用ascii字符,这就导致无法发送非ascii字符,图片等二进制文件。所以在MIME对电子邮件进行了扩展,该扩展协议就指定了内容的传输的编码格式,可以是base64,base64编码使得在电子邮件中传输图片成为可能。

  • base64是加密算法吗?

base64不是加密算法,他只是一种编码方式,数据从一种形式转换为另一种形式进行传输/存储。

前端常见应用


接下来,我们了解下前端开发中常见的对Base64编码的一些使用场景。
Base64在前端方面的应用,多数都是针对图片的处理,一般都是基于DataURL的方式来使用。

Data URL 由 data:前缀MIME类型(表明数据类型)base64标志位(如果是文本,则可选)以及 数据本身 四部分组成。
具体的格式:data:[<mime type>][;base64],<data>
这里的第四部分 <data> 数据本身,就是一个Base64字符串。

小图片转码


即开篇说的针对图片优化,使用Base64能减少请求数的,可以在img标签下,或者css中:

<img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0c......Ii8+PC9nPjwvc3ZnPg==">

.icon {  background: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0c......Ii8+PC9nPjwvc3ZnPg==);}

当我们使用vue或react框架时,也可以通过url-loader来配置,图标转Base64的大小:

  .loader('url-loader')  .tap(options => {    options.limit = 10240 // 10kb    return options  })

Java中实现Base64


Java已经替我们写好Base64的实现细节,使用的时候直接调用即可。具体代码如下所示:

总结


在计算机中一个字节共有256种,即ascii码表,而ascii码的128~255之间的值是不可见字符,对于一些只支持可见字符的协议,比如邮件传输协议(SMTP)只支持可见的ASCII字符的传递,如果要传输二进制文件,比如:图片、视频是无法实现的,因此就有了base64编码格式,Base64编码格式对于所有二进制格式的数据,都可以转化为可显示的字符。

  • 在网络上交换数据时,比如说从A地传递到B地,往往要经过多个路由设备,由于不同的设备对字符的处理方式有一些不同,这样那些不可见字符就有可能被处理错误,不利于传输。
  • http先以当中的key-value字段,必须进行url编码,不然出现的等号或者空格可能是解析失败。
  • 有些文本协议不支持可见字符传输,比如简单邮件传输协议(SMTP)。
  • 将二进制数据转为字符串(ASCII码),方便数据传输。
  • 网页中内嵌简单图片,浏览器能直接展示Base64编码图片,减少请求。
  • 编码后数据会大至少三分之一,需要额外的方法处理编解码。
  • Base64是网络上最常见的用于传输8Bit字节码的编码方式之一,Base64就是一种基于64个可打印字符来表示二进制数据的方法
  • Base64是一种任意二进制到文本字符串的编码方法,常用于在URL、Cookie、网页中传输少量二进制数据。
  • Base64是一种编码算法,而非加密算法