Unicode,UTF-8,UTF-16和JavaScript

过去一直没搞懂Unicode和UTF-8之间的关系,对JavaScript中为什么无法正确表示某些四字节字符也懵懵懂懂,看了阮一峰大神的博客之后终于搞明白了,这篇是我的整理。

1、什么是Unicode字符集

我们都知道,在计算机里面,所有的内容都是以二进制的形式存在,那么可以很自然的想到,当我们要表示一个字符时,当然也是用一个二进制来表示,也就是说其实是用一个二进制数字来表示字符。但是问题是,对于使用哪个二进制表示哪个字符,每个人都有自己不同的想法,如果大家各自按照自己的想法来设置,显然不利于传输。这就像如果没有普通话,大家都说方言的话,可想而知,在交流上会有多么困难。因此,如果能有一个标准的字符集,规定了字符的二进制表示,那么我们在传输信息的时候就会更便捷。Unicode就是这样一个字符集。但是我们先不说Unicode,先来说说ASCII

ASCII也是一个字符集,它是美国的一个字符集,算是最早一批的字符集了。ASCII一共有128个字符,包括a-zA-Z0-9等等常用的字符。例如a对应的ASCII值为97ASCII字符集使用一个字节表示字符,由于只需要128个字符,因此第一位固定为0。例如上述的aASCII就使用01100001表示。对于美国人来说,这些自然足够了,但是对其他国家来说,这是远远不够的,比如我们伟大的中文,至少有上万个字符。因此为了更广泛更便捷的传输信息,需要一个统一的,更大的字符集,Unicode诞生了。

Unicode团队于1988年成立,到现在也已经过去三十多年了,Unicode字符集也在不断的壮大,甚至出现了一些表情符号。Unicode字符集从0开始编号,如下,U+0000表示字符null

U+0000 = null

上面的式子中,U+表示这是Unicode字符,后面的数字表示每个字符对应的码点(code point),通常用16进制表示。在JavaScript中可以通过\u的方式直接使用Unicode字符。如下:

'\u82b1' === '花'; // true

如果要在html中使用Unicode字符,则需要使用&#,加上Unicode字符编码,最后别忘了分号。注意在html中使用Unicode字符需要用十进制。如:Unicode字符编码为U+3048,转为十进制为12306

<div>&#12360;</div> <!--え-->

Unicode目前的版本是13.0,共收录了143,859 个字符。其中常用的有65536个。这么多符号,Unicode不是一次性定义的,而是分区定义。每个区可以存放65536个(216)字符,称为一个平面(plane)。目前,一共有17个(25)平面,也就是说,整个Unicode字符集的大小现在是221

最前面的65536个字符位,称为基本平面(缩写BMP),它的码点范围是从0一直到216-1,写成16进制就是从U+0000到U+FFFF。所有最常见的字符都放在这个平面,这是Unicode最先定义和公布的一个平面。

剩下的字符都放在辅助平面(缩写SMP),码点范围从U+010000一直到U+10FFFF

2、UTF-8和UTF-16

Unicode只是一个字符集,要在计算机上显示还需要进行编码。因为不同的字符需要的字节数是不一样的。例如英文字母a只需要一个字节,但是中文字符就需要两个字节。计算机是个傻乎乎的东西,它只会根据某个规则去进行读取,无法自行判断两个字节到底是表示一个字符还是两个字符。因此需要对Unicode字符进行编码,通过设置成固定格式来使得Unicode字符具有规律。从而能够被计算机识别。UTF-8UTF-16就是这样一种编码规则。

UTF-8的编码规则是这样的:

  • 对于单字节的字符,其实也就是对应ASCII字符集里面的字符,第一位固定为0,后面7位对应的是字符的码点。因此Unicode字符集兼容ASCII字符集。
  • 对于n字节的字符,第一个字节前n位固定为1,第n+1位固定为0,其余字节都是以10开头。除去这些固定的部分,剩余的部分对应字符的Unicode码点。

根据上面的规则,可以得到下面的表格:

Unicode符号范围(16进制)UTF-8编码方式(二进制)
0000 0000 ~ 0000 007F0xxxxxxx
0000 0080 ~ 0000 07FF110xxxxx 10xxxxxx
0000 0800 ~ 0000 FFFF1110xxxx 10xxxxxx 10xxxxxx
0001 0000 ~ 0010 FFFF11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

根据这张表,我们可以看出,如果第一个字节以0开头,那么它就是一个单独的字符,并且位于ASCII字符集内。如果第一个字节以1开头,那么就可以通过判断有几个连续的1来判断这个字符使用几个字节表示。下面以汉字表示如何使用UTF-8表示字符:

Unicode码点是54CE,根据上表,显然需要3个字节,因此对应的格式应该为1110xxxx 10xxxxxx 10xxxxxx。先将码点转换成二进制:101010011001110,然后从右往左将码点填充进去,覆盖原有的x,剩余的x0替换。可以得到UTF-8表示法为:11100101-10010011-10001110。这里为了表示清晰,使用-进行了分割。转化为16进制为E5938E。这里补充一个小知识,不保证其通用性。通常我们在发ajax请求时,如果url中含有中文,浏览器会自动将其编码,然后就会出现%E5%93%8E这种东西。是不是很熟悉?其实我们可以明显看出这就是转成了UTF-8表示法,不过是在每个字节的前面加了%。因此当我们想要得到一个中文字符的UTF-8编码时,可以使用encodeURI,将得到的结果里面%去掉即可。例如:encodeURI('哎').replace(/%/g, '');

上面讲了UTF-8编码方式,其实编码方式有多种,接下来再简单讲讲UTF-16UTF-8UTF-16的区别就在于UTF-8是长度可变的,可以是一个字节表示一个字符,也可以是2个字节表示一个字符。而UTF-16固定使用2个字节和4个字节,分别用来表示基本平面辅助平面。由上文可知,基本平面,也就是最常用的那65536个字符,只需要2个字节(U+0000到U+FFFF)就可以,辅助平面则需要使用4个字节(U+010000到U+10FFFF)。那么怎么判断4个字节是表示一个字符还是两个字符呢?这块挺有意思。在基本平面内,从U+D800U+DFFF是一个空段,即这些码点不对应任何字符。因此,这个空段可以用来映射辅助平面的字符。而辅助平面字符位共有220个,也就是说,对应这些字符至少需要20个二进制位。UTF-16将这20位拆成两半,前10位映射在U+D800U+DBFF(空间大小210),称为高位(H),后10位映射在U+DC00U+DFFF(空间大小210),称为低位(L)。这意味着,一个辅助平面的字符,被拆成两个基本平面的字符表示。所以,当我们遇到两个字节,发现它的码点在U+D800到U+DBFF之间,就可以断定,紧跟在后面的两个字节的码点,应该在U+DC00到U+DFFF之间,这四个字节必须放在一起解读。UTF-16还有个转码公式,可以看阮神的博客

3、JavaScript和UTF-16

那么,JavaScript用的是哪种编码方式呢?UTF-16?还是UTF-8?,都不是。它用的是UCS-2。。。这又是个什么东西?请听我慢慢道来。

上文已经谈到,Unicode团队于1988年成立,他们野心勃勃的想搞一个统一的字符集。但是在1989年,还有一个组织也想搞一个这玩意,这个组织名字叫做UCSUCS后来先上,在1990年就公布了第一套编码方法UCS-2,使用2个字节表示已经有码点的字符。(那个时候只有一个平面,就是基本平面,所以2个字节就够用了。)UTF-16编码却迟至1996年7月才公布。而JavaScript在1995年设计完成,那么关系就很清楚了。好在1991年10月,两个团队就决定合并字符集,UTF-16明确宣布是UCS-2的超集,即基本平面字符沿用UCS-2编码,辅助平面字符定义了4个字节的表示方法。

以上。

4、参考链接

  1. 阮一峰-字符编码笔记:ASCII,Unicode 和 UTF-8
  2. 阮一峰-Unicode与JavaScript详解
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值