计算机内部使用二进制存储数据,只认识0和1两个数字,计算机的世界只有0和1。但我们的世界却充满着文字,如a, b, c。怎样才能让计算机显示文字,供我们使用和交流?只能先把文字转化成数字进行存储,然后再把数字转化成文字进行显示,但一个文字怎么转化成数字,转化成哪个数字,或者用哪一个数字代表哪一个文字呢?

  如果在我的计算机上,1 表示A,2 表示B,3 表示C, 依次类推。我写HELLO,实际存储到计算机中的是数字8, 5, 12, 12, 15,我发HELLO给你,实际就是把这些数字(8, 5, 12, 12, 15)通过网络发送给你。但在你的计算机上,0表示A,1表示B,当你的计算机接受到(8, 5, 12, 12, 15)并解析时,8表示的却是I, 最终你收到的是IFMMP,意思完全变了,不同的计算机之间没有办法进行沟通。为了有效沟通,我们需要制定一个标准来规定哪一个数字代表哪一个文字,对文字进行编码,并且所有计算机都要遵守这个标准。

  19世纪60年代, 美国标准委员会制定了ASCII码,使用7个bit(7-bit)来编码一个文字. 7个bit(从0000000 to 1111111)能够表示128个值(0-127),所以ASCII码有足够的空间来表示大写字母,小写字母,数字等。在1968年,美国所有的计算机都使用ASCII码。当我再发送HELLO 给你,HELLO编码成了72, 69,76,76, 79 ,在计算机内部使用二进制表示就是1001000,10000101,1001100,1001100,1001111。这些数字到达你的计算机,由于也是使用ASCII 码进行解析,所以能够正常显示为HELLO,我们能用计算机地进行有效沟通了。

  19世纪70年代,计算机普及到了欧洲,欧洲各国也想使用计算机表示自己的文字,比如希腊人想表示欧米茄ω.  俄罗斯人想表示Я,这些都是ASCII 码中没有的。这时计算机硬件也得到了发展,CPU能一次处理8个bit,计算机的最小存储单位也变成了8bit(一个字节)。8个bit能表示256个值(0-255), 而ASCII码最多使用到127, 还剩下128-255 是没有用到。他们就想用这些没有用到的数字来表示各自的文字。最开始的时候,IBM就用128-255 这些数字表示一些有符号的字字母和希腊字母,比如224表示希腊字母α。然而不像ASCII码,128-255能表示什么字符从来没有被标准化。欧洲各国开始,自己造自己的数字与字母的对应关系(字符集),于是出现了个各种各样的字符集。比如俄罗斯人用244表示俄语 Я, 然而希腊人却用它来用表示小写的欧米茄,ω. 计算机之间的交流又陷入了混乱。

  19世纪90年代末,做了一次标准化的尝试和努力,创造出了15种不同的的字符集,它们都使用8个bit表示一个字符,叫做ISO-8859-1, ISO-8859-2, 直到 ISO-8859-16,中间的ISO-8859-12被废弃了。俄罗斯人使用的是ISO-8859-5, 224 表示字符 p,207 代表Я。 如果俄罗斯朋友给你发送了一封邮件或一份文档,最好知道他使用的是哪一种字符集。我们接受的邮件或文档是序例化的数字,数字224代表可能代表a, p 或 Я。如果我们使用了错误的字符编码打开文档,看到就是乱码。

  信息可以在不同的计算机上交流,但是你要确定使用的是哪一个字符编码。但随着计算机的进一步普及,它来到了亚洲,中日韩有大量的文字,8个bit, 256个值根本放不下,于是中国创造了GBK, 使用两个字节进行表示。国际化的到来,国与国之间沟通更为频繁,使得这种知道编码的交流方式愈发麻烦。

  Unicode诞生了。它的想法很简单,就是给世界上每一个国家的每一个文字都分配一个独一无二的数字,这个数字称之为码点(code point), 前127个码点和ASCII一样,128-255基本上和ISO-8859-1一致,256之后就是其它有符号的字符,880 之后是希腊字母,中国,日本和韩国字符从11904开始。这样就不会产生混乱了,不管是哪个国家,哪种字符,都有一个唯一数字进行对应。俄罗斯的Я 永远都是1071.希腊字母阿尔法α永远都给是945。不过官方的unicode码点是用16进制进行表示,前面加上U+(表示unicode), H的unicode码点通常写成U+0048,而不是10进制的72。字符串Hello, 在unicode中对应5个码点:U+0048  U+0065  U+006C  U+006C  U+006F,但他们仅仅是数字,并没有说怎么存储到计算机中。Unicode只是提供了一个设想,并没有提供具体的实现,Unicode 最大的问题是它包含了太多的字符,大大超出了256,8个bits已经放不下了。

  最初(19世纪90年代)的时候,由于各国的文字都比较少,只有7,161 个,而在计算机中,两个字节(16个bit)可以表示6万多个值,足够表示7千多个数字了, 所以决定使用两个字节来存储一个码点,这就是UCS-2编码, Hello变成了00 48 00 65 00 6C 00 6C 00 6F。但有一个比较尴尬的事情是,由于一个字符占用两个字节,对欧美人来说,有点浪费存储空间,他们也不愿意转换成unicode。有很长一段时间,他们忽略了unocide.

  但是随着时间的推移,尤其是亚洲国家把大量的象形文字也加入到Unicode 编码中,65536的数量也放不下这些文字,于是Unicode 编码字符集也相应的进行了扩充,它把整个字符集分成了17个平面,每一个平面都能放2^16 个文字, 每一个平面的码点从U+n0000 到U+nFFFF结束。n是用0x0 到0x10, 16进制表示

  第一个平面Plane 0放置的就是最初的UCS-2编码中所定义的文字(最常用的文字),叫做基本多语言平面Basic Multilingual Plane (or BMP),基本多语言平面码点的取值范围是0x0000 – 0xFFFF, 其他的16个平面称为补充平面(Supplementary Planes or Astral Planes), 它们的取值范围从0x10000 开始 (0xFFFF + 1 就等于0x10000,  16进制的计算)。针对这种多平面的字符集,UCS-2编码方式肯定是不合适了。最简单的方式,使用更多的字节来存储一个文字。此时计算机也得到了发展,CPU都是32bit 或64-bit, 一次能处理32个字节或64个字节。那为什么不能用更多的bit来表示一个字符? 当然可以,UTF-16, UTF-32 出现了,取代了UCS-2

  UTF-16 就是对于在BMP 平面的文字用两个字节(16个bit)进行存储,然后对于其他平面的文字用四个字节进行存储。

  UTF-32 则是用32个bit, 4个字节来存储一个字符,所有的字符都占用4个字节。

  使用UTF-16或UTF-32有什么问题呢?文件转输。如果你写的都是英文字符,使用一个字节进行存储就可以了,但如果使用UTF-16,那就要占两个字节,UTF-32 就要使用4个字节,浪费带宽。最终UTF-8 出现,才解决这个问题。UTF-8 使用的是变长字节。码点从0-127 使用一个字节进行存储,只有码点大于128,才会用2个,3个,最大到6个字节进行存储。使用ASCII码写的文档,也是有效的UTF-8文档。 Hello的码点是 U+0048 U+0065 U+006C U+006C U+006F,   存储码计算机中 48  65  6C  6C 6F, 和 ASCII 一样。

  前端开发,使用的是UTF-8和UTF-16。无论写什么,html, css, 或是js, 首先要做的就是把文档格式改为UTF-8, vs code 右下角的select Encoding 

  HTML 文档的meta 标签也要设置成UTF-8, 

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">

  为什么呢?就是因为UTF-8可以减少文件大小,浏览网页,实际上是向服务器请求html, js 等静态资源文件,文件越小,传输越快。当浏览器得到这些资源文件时,要对其进行解析,文件是用UTF-8 写的,也要用UTF-8 进行解析,否则出现乱码。那怎么告诉浏览器用UTF-8 进行解析?charset 设定字符编码。浏览器解析html文档时,先按操作系统默认字符编码进行解析,如果它碰到<meta http-equiv=”Content-Type” content=”text/html; charset=utf-8″>,它会停止解析,按charset 设置的utf-8 重新进行解析,这也要求设置charset的meta标签尽可能早的出现在<head> 标签中。

  这时,可能会有疑问,书写JS代码使用UTF-8, 但JavaScript 内部使用的是UTF-16,这怎么理解?JS引擎在内部做了一次UTF-8到UTF-16的转换。当浏览器解析完js代码,js引擎执行js代码的时候,如果它碰到字符串,它就会把它转成换UTF-16. JavaScript 的源代码文件也可以是任意格式,但源代码中的字符串一定是UTF-16. While a  javascript  source file  can  have  any  kind  of  encoding,  JavaScript  will  then  covert it  internally  to  UTF-16  before  executing  it.  JavaScript  strings  are  all  UTF-16    sequences,  as  the  ECMAScript  standard  says:

  When a String contains actual textual data, each element is considered to be a single UTF-16 code unit

  JavaScript使用UTF-16, 重点说一下UTF-16。UTF-16,以16-bit为基础进行存储,如果16-bit 存储不了,再增加一个16-bit 进行存储,以16-bit为单位进行递增。对于unicode 字符集来说,BMP 平面中的文字用一个16-bit进行存储,因为BMP平面最多存放65536个字符,16个bit完全可以搞定,其他平面的文字就要用(2个16-bit)进行存储。16-bit 也称为码元(code unit)。一个16bit 就是一个码元, 两个16-bit 就是两个码元。码点是unicode字符集中的术语,仅仅是一个普通的数字,没有涉及到任何计算机的存储,码元则是指字符在计算机中所占的物理存储空间,占了多大内存。UTF-16 等编码,就是规定字符或码点是怎么存储到计算机中的,一个字符,到底占用多少个bit,多大的内存空间,把码点转化成码元。

  其他平面中的文字怎么用两个码元(16-bit)来显示呢? 先看一下目前Unicode 非基本面的有多少个字, 一共16个面, 一个面是2^16 个字,那也就是说,一共有16 * 2^16 个字, 就是2^20个字符, 也就需要20个二进制bit 位,一个码元只能16bit 位,那只能把20bit 位进行拆分成2个部分,一个部分占10个bit, 这样一个码元就可以表示了。但这又存在另外一个问题,当计算机去读取字符的时候,它碰到一个字节就去读取一个字节,它并不知道,你这个字节是拆分出来的,你会发现,本来一个字,确显示出了毫不相干的两个字,这怎么办呢?在BMP平面,它所定义的Unicode 字符(55444个)并没有完全使用了2个字节,有一部分是空的,并没有对应任何字符,这一段是0xD800 到 0xDFFF(55296 – 57343), 这也就意味着,使用这一段上的码元没有任何问题。那我们就想办法,让非基本面的码点对应到这一段上来。对应的逻辑是这样的,首先把0xD800 – 0xDFFF  分为两个部分,0xD800 到 0xDBFF 和0xDC00 到 0xDFFF,然后再用码点减去65536, 把剩下的数用二进制表示,如果二进制不够20位,可以在前面补0.  再把20位二进制分为两个部分,每个部分占用10个bit,  前10个bit 对应到0xD800 – 0xDFFF, 称为高位(H), 后10个bit 对应到0xDC00 到 0xDFFF 称为低位(L), 这样一个码点就可以用两个码元(高位, 低位)进行表示了。映射比较复杂,不过Unicode 提供了一个公式,可以直接通过码点得到高低位码元。

H = Math.floor((c-0x10000) / 0x400)+0xD800  // c 是码点 0x10000 就是65536  0x400 就是1024, 0xD800 就是H 开始位置

L = (c - 0x10000) % 0x400 + 0xDC00  // 0xDC00  L 位开始的位置。 

  举一个小例子,汉字”

版权声明:本文为SamWeb原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/SamWeb/p/13113009.html