目录

计算机编码的简介

1.简介

程序最终都是以二进制的形式存在计算底层,也就是以0、1这样的字符排列组成程序运行的信息。我们知道计算机最小的存储单位叫做bit(位),8bit也就是一个字节(byte)。我们在电脑上的输入又或是编写的程序,最终都会转化成这样的形式去运行,那么转换成二进制就需要一个标准

2.编码的发展过程

2.1.ASCII 编码

最开始的计算机,只存在于欧美的一些发达国家中,他们的程序中只需要他们所理解的字符、字母以及阿拉伯数学,因此,他们在编码解码的时候只需要考虑这一种情况:如何将这些字符、字母、数字转为计算机所能理解的二进制数。基于这个需求,就出现了ASCII编码

https://i.loli.net/2021/10/17/w4SQqbaCDWZNPOt.jpg

可以看到ASCII编码从0开始最多到127,也就是最多表示了128个字符,转化成2进制,也就是1111111,前面补码一位0,也就是01111111,这里最多也就是8位,也就是一个字节,这也就是说明最多英文字符只需要占一个字节就能表示了

题外话:后来ASCII也进行了一个拓展,包含了更多的符号以及拉丁字母,最多可表示256个字符,也就是一个完整的1字节了

2.2.Unicode

Unicode是一个字符集,上面说了ASCII最多只能表示128/256个字符,那后来各国渐渐都有计算机的发展,需要不同的编码方式,计算机也需要识别更多的字符,这个时候,对于编码标准就要进行拓展了。比如果我国就有GB2312、GBK等汉字字符标准,这些标准除了兼容ASCII的128个字符意外,还新加了几万个汉字以及符号。那么在一个互联的时代,各国的字符应该统一起来成为一个标准,需要一个字符集去包含所有的字符,这就是Unicode。Unicode将世界上所有的符号都纳入其中,成功实现了每个数字代表唯一的至少在某种语言中使用的符号,目前,Unicode 字符集中已经收录超过 13 万个字符。https://home.unicode.org/

Unicode字符的编号表示的形式为 U+[XX]XXXX,X 代表一个十六制数字,一般可以有 4-6 位,不足 4 位前补 0 补足 4 位,超过则按是几位就是几位,具体范围是 U+0000~U+10FFFF,U+为Unicode开头,后面的编码则是十六进制表示的编号,U+10FFFF中的10FFFF转为十进制则是1114111,也就是说Unicode最多表示111万个字符,按照目前已经收录的10几万个字符来看,还有很大的空间可以待用

Unicode是字符集,他最终的编码方案有三种分别是 UTF-32、UTF-16 和 UTF-8,这使他与之前的编码模式不同,什么叫编码方案,简单的说就是决定了Unicode字符在计算机里面最终以多少字节去存储的方式。首先还要明确一个概念,定长与变长,定长的意思就是说某个字符固定用几个字节去存储,而变长的意思代表根据实际情况,选择性的用几个字节去存储

我们以字为例:它的编号或者叫码点为U+7535,7535 转为二进制为 1110101 00110101,其中1110101 不够8位,所以前面补位一位0,变成01110101 00110101

https://z3.ax1x.com/2021/10/18/5afAu6.png

UTF-32

采用定长的编码方式,以四个字节去存储字符,那么上面的二进制不满四个字节,前面就填充为0,所以这个字采用UTF-32编码则为

00000000 00000000 01110101 00110101

UTF-8

采用变长的编码方式,它采用1~4个字节去表示一个字符

1.对于只有一个字节的符号,字节的第一位设为0,后面 7 位为这个符号的 Unicode 码。此时,对于英语字母UTF-8 编码和 ASCII 码是相同的

2.对于 n 字节的符号(n > 1),第一个字节的前 n 位都设为 1,第 n + 1 位设为0,后面字节的前两位一律设为 10。剩下的没有提及的二进制位,全部为这个符号的 Unicode 码,如下表所示:

Unicode 码点范围(十六进制) UTF-8 编码方式(二进制) 字节数
0000 0000 ~ 0000 007F 0xxxxxxx 一个字节
0000 0080 ~ 0000 07FF 110xxxxx 10xxxxxx 二个子节
0000 0800 ~ 0000 FFFF 1110xxxx 10xxxxxx 10xxxxxx 三个字节
0001 0000 ~ 0010 FFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx 四个字节

这里还是以电字为例,它的码点为U+7535,对比上表,发现它在上表的第三行,意思就是需要使用三个字节去编码,它的UTF-8编码方式就是1110xxxx 10xxxxxx 10xxxxxx。现在问题就是这个*****代表什么,其实就是从该字符的最后一个二进制位开始,依次从后向前填入格式中,多出的位补 0。电字的二进制表示为1110101 00110101,那么按照上诉规则依次往前面添加。最终UTF-8的编码二进制表示为

11100111 10010100 10110101

这个二进制编码转为16进制编码表示为E794B5,这和unicode官方表也对的上了

UTF-16

属于变长编码, 它采用2~4个字节去表示一个字符,具体表示方法

Unicode范围 UTF-16编码方式
U+000~U+FFFF 2 Byte存储,编码后等于Unicode值
U+10000~U+10FFFF 4 Byte存储,现将Unicode值减去(0x10000),得到20bit长的值。再将Unicode分为高10位和低10位。UTF-16编码的高位是2 Byte,高10位Unicode范围为0-0x3FF,将Unicode值加上0XD800,得到高位代理(或称为前导代理,存储高位);低位也是2 Byte,低十位Unicode范围一样为0~0x3FF,将Unicode值加上0xDC00,得到低位代理(或称为后尾代理,存储低位)

1.第一个例子:电字 U+7535,也就是对比上表,在2个字节范围内,所以编码后的位置75 35,UTF-16编码就用7535表示

2.第二个例子比较特殊一点: U+12345,这是一个符号𒍅。它在上表中处于第二行,它的位置计算步骤为

第一步:16进制12345减去十六进制10000,得到2345,2345的二进制为10001101000101

第二步:将Unicode分为高10位和低10位,低十位从某位往前数,高十位缺0补0,得到低十位11 0100 0101、高十位00 0000 1000

第三步:高十位加上D800,这里需要将00 0000 1000转为十六进制8,加上D800得到D808,同理,低十位的加法得到DF45。于是得到最终结果D8 08 DF 45,它的16进制编码表示就是D808DF45

2.2 Base64编码

为什么会有Base64编码呢?因为有些网络传送渠道并不支持所有的字节,例如传统的邮件只支持可见字符的传送,像ASCII码的控制字符就不能通过邮件传送。这样用途就受到了很大的限制,比如图片二进制流的每个字节不可能全部是可见字符,所以就传送不了。Base64编码应运而生,Base64就是一种基于64个可打印字符来表示二进制数据的表示方法

下面是Base64索引表,可以看到Base64编码可以用"A-Z、a-z、0-9、+、/” 64个可打印字符来表示所有其它的二进制数据,它的数值表示字符的索引,这个Base64索引表中只有64个字符,也就是用6bit,1个字节内完全可以表示完Base64编码中的字符,同时还多出两位需要补0。同时Base64还有个基本的规定,就是至少用4个字符去表示源字符

https://z3.ax1x.com/2021/10/20/50G4e0.png

例子1:将文本Man转为Base64编码

https://z3.ax1x.com/2021/10/20/50J16s.png

第一步:找到M的ASCII编码为77,转为8位的二进制01001101,同样a转为01100001,n转为01101110,排列出来就是

01001101 01100001 01101110

第二步:以每6位去划分

010011 010110 000101 101110

第三步:将每一段的二进制转为十进制索引

19 22 5 46

然后对照上表,找出对应的字符,那么Man这个文本转为Base64编码也就是TWFu

例子2:将文本A、BC转为Base64编码

https://z3.ax1x.com/2021/10/20/50Jvcj.png

先看字母A

第一步:将A的ASCII索引65转为二进制1000001,同样前面补0变成

01000001

第二步:以每6位去划分

010000 01

第三步:可以看到01后面就没位数了,这个时候就要用0去补全6位,也就是

010000 010000

然后Base64编码的基本规则是以至少以4个字符去表示源字符,那么剩下的两个字符就用=去表示,同时,将已有的二进制位转为十进制Base64索引找出对应的字符,那么最终A转为Base64编码就是QQ==,后面的BC文本也是一个理

需要注意的一点就是:Base64编码用于请求资源或者转发资源时去表示那些不可见字符,有的时候我们在网上留一些个人信息,为了不被SEO,也可以先将我们的手机号、邮箱转为Base64。Base64不是专为ASCII里面的字符去服务的,其他的二进制数据,也可以转为相应的Base64编码,比如前端里面的二进制图片信息去转为Base64编码,这就是它的由来

这里提供一个将ASCII中的字符转为Base64编码的方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
function tranverse(str) {
  let code = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
  let res = ''
  let binaryStr = ''

  for (let i = 0, max = str.length; i < max; i++) {
    let temp = str.charCodeAt(i).toString(2)
    binaryStr += new Array(9 - temp.length).join('0') + temp
  }

  let tail = binaryStr.length % 6
  let left = binaryStr.substr(0, binaryStr.length - tail)
  let right = binaryStr.substr(binaryStr.length - tail, binaryStr.length)

  for (let i = 0, max = left.length; i < max; i += 6) {
    let temp = left.substr(i, 6)
    let index = parseInt(temp, 2)
    res += code[index]
  }

  if (tail) {
    right = right + new Array(7 - right.length).join('0')
    res += code[parseInt(right, 2)]
    res += new Array((6 - tail) / 2 + 1).join('=')
  }
  return res
}

将图片转化为Base64,其原理是通过创建Canvas元素,然后加载图片,将图片画到Context上,然后调用toDataURL方法直接转成Base64编码返回

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function getBase64Image(url) {
  const img = new Image()

  // 如果是第三方图片,一定要确定是否能跨域
  // img.setAttribute('crossOrigin', 'anonymous')

  img.src = url

  return new Promise((resolve, reject) => {
    img.onload = () => {
      const canvas = document.createElement('canvas')
      canvas.width = img.width
      canvas.height = img.height
      const ctx = canvas.getContext('2d')
      ctx.drawImage(img, 0, 0, canvas.width, canvas.height)
      resolve({
        base64: canvas.toDataURL()
      })
    }
    img.onerror = () => {
      reject()
    }
  })
}

3.参考链接

1.深入理解字符编码

2.Unicode字符集