以操作系统的角度去看编码和解码

2,824 阅读8分钟

前言

在编辑器中输入信息,再保存起来,对我们来说,是再日常不过的事;但有没有想过,在保存信息时,操作系统是怎么把信息存储起来的?在我们打开这个文件时,操作系统又是怎么把这些信息展示给我们的?这就涉及到了编码和解码的两个概念。

编码和解码

我们在桌面上创建一个 txt 文件,并写入‘abc’,

当我们保存这个 txt 文件时,操作系统是怎么去保存这个 txt 文件里的内容?

因为计算机是由无数个逻辑电路组成的,只能根据 0 和 1 的无限位数和组合来表达信息,所以我们想要在计算机内存储信息的话,计算机就得把这些信息按照一定的编码方式(比如 ascii、uft-8)变成0,1(这里的 0 和 1 并不是数字的 0 和 1,0 和 1是表示两种不同的状态,0 表示低电平,1 表示高电平)的组合,这个过程我们就称为编码;

我输入的信息是 ‘abc’,那么根据 ascii 码,编码出来的结果就是 011000010110001001100011。

当我们打开这个 txt 文件时,我们知道,操作系统是获取到了由 0,1 组合起来的信息,那么,它是怎么把 0,1 转变成我们写入的信息?

现在操作系统读取到了我们在 txt 文件中输入的信息编码后的二进制码, 011000010110001001100011,他会根据每8位的二进制码,去 ascii 表找对应的信息,先取出 01100001,找到是 a,再取出 01100010 找到是 b,最后取出 01100011 找到是 c,这个过程我们称为解码。

但是操作系统是怎么知道根据每8位去读取二进制码,而不是每10位,或者是每16位去读取?

当我们用编辑器去编写、保存信息时,我们的编辑器会指定一个编码方式,这个编码方式就会告诉操作系统,每一个字符(a 是一个字符,b 是一个字符)是用多少位二进制码表示,比如 ascii 码是每8位表示一个字符(单字节字符),gbk 码是每 16 位表示一个字符(双字节字符),utf-8 是变长的,也就是既可以每 8 位表示一个字符,又可以每 16 位表示一个字符串,至于具体的原理是什么,我们后面会说到。

ascii 码

最开始计算机是美国人发明的,而英文字母只有 26 个,再加上阿拉伯数字、标点符号(,.!等)、特殊符号(@#$%^&等)以及一些具有控制功能的字符(往往不会显示出来),也就收录了 128 个字符,所以只需要 8 位二进制就可以把 128 个字符表示完全,也就是 00000000 - 011111111。

下面是我在掘金搬运某个博主的方法,大家可以在控制台试一下,然后对照着这个表看看,输出的结果会省略前缀的 0。

  function strToBinary (str) {
      let list = str.split('');
      return list.map(item => {
          return item.charCodeAt().toString(2);
      }).join(' ');
  }

中文编码

当中国也有了计算机之后,为了显示中文,必须设计一套编码规则用于将汉字转换为计算机可以接受的数字系统的数,也就是以 gb 开头的编码方式,比如 gb2312,gbk 等;

GB2312 是一种双字节编码,可以表示 6763 个汉字和字符;GBK 是 GB2312 的扩展,可以表示 21003 个汉字和字符;

这里了解一下就好。

unicode 码

世界上存在着多种编码方式,同一个二进制数字在经过不同的编码方式编码过后,很有可能会生成不一样的二进制码,那么,编码方式不统一会造成什么问题呢?

比如现在有 a、b 两种编码方式,阿拉伯数字 1 经过 a 编码方式转变成了 00000000,而经过 b 编码方式变成了 11111111,当小明在家里的电脑指定了用 a 的编码方式保存了某个 txt 文件,然后把这个 txt 文件发送给了小红,但是,小红家的电脑保存了很多种编码方式,唯独没有 a 编码方式,所以当小红打开 txt 文件时,就只能用默认的编码方式去解码,而里面的数字 1 就有可能会变成其他阿拉伯数字或者直接乱码。

所以,unicode 的出现就是解决这样的问题,将世界上所有的符号都纳入其中,每一个符号都给予一个独一无二的编码,那么乱码问题就会消失。

需要注意的是,Unicode 只是一个符号集,它只规定了符号的二进制代码,却没有规定这个二进制代码应该如何存储。

这句话是什么意思呢?我们说 ascill 码中字符 a 的二进制代码是 01100001,那么它写入磁盘的也会是 01100001,因为 ascill 码中所有的二进制代码都是 8 位的;

但是对于 Unicode 来说,里面的符号有很多,那么它势必要用多字节去存储对应的符号,比如汉字‘掘’可能是1010101010101010,但是英文字母 a 却是0000000000000001,如果大家都用16位的二进制去存储,英文字母 a 就会有大量的前缀0,这样无疑是浪费存储空间的,所以 Unicode 仅仅是规定了符号的二进制代码,却没有规定这个二进制代码应该如何存储。

utf-8

UTF-8 就是 Unicode 的实现方式之一。

UTF-8 最大的一个特点,就是它是一种变长的编码方式。它可以使用1~4个字节表示一个符号,根据不同的符号而变化字节长度。它有两个规则

  • 对于单字节的符号:字节的第一位设为0,后面7位为这个符号的 Unicode 码。因此对于英语字母,UTF-8 编码和 ASCII 码是相同的;

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

我们用一段二进制码去举例解释上面的文字

0110000111100100101110001010010101100010

大家可以在这个网站上去把二进制转化为汉字,或者把汉字转换为二进制,注意选择的转换方式要选对

拿到这串组合后,

  • 首先先判断第一位,发现是 0,运用第一条规则,拿前8位( 01100001 )去 unicode 表中查找,因为 unicode 是四字节转码,所以我们需要补满0( 00000000000000000000000001100001 ),在网站上转换出来是字符 ‘a’;
  • 接着判断第9位,发现是1,再判断第10位,发现是1,再判断第11位,发现是1,再判断第12位,发现是0,确定了下一个字符是三子节字符,取出第9到32位 111001001011100010100101,然后运用第二条规则后得到 0100111000100101,补满0( 00000000000000000100111000100101 ),在网站上转换出来是字符 ‘严’;
  • 接下来拿到第33位,发现是0,运用第一条规则,拿出 01100010,补满0( 00000000000000000000000001100010 ),在网站上转换出来是字符 ‘b’;
  • 用unicode表推导出来的信息是:a严b
  • 直接复制 0110000111100100101110001010010101100010,在网站上转换出来信息是 a严b;
  • 推导成立

总结

刚开始接触编码和解码的话,或许会被很多专业术语或者是二进制码绕晕,实际上,我们只要搞清楚在操作系统中,编码和解码的整套流程,那么就能很好的理解这些专业术语以及它们之间的关系;

至于像 ascill、gbk、unicode、utf-8 这样一大堆的编码方式,我们其实并不需要每一个都去了解的很透彻,毕竟很多东西都是历史遗留的产物,只需要了解unicode 和 utf-8即可。