字符编码技术专题(五):前端必读的计算机字符编码知识入门

admin 2025-08-21 09:04 603人围观 ,发现603个评论
1、引言

作为开发人员,工作中我们可能会遇到以下问题:

1)可能你知道JavaScript中''.length=2,但''.length呢?

2)困惑于Unicode和UTF-8的关系?

3)学计算机时会遇到这样的提问:一个汉字是几个字节?

4)读取二进制数据时,为何有大端序小端序的分别?

5)为何UTF-8文件最好存储为无BOM头格式?

6)数据乱码时总会看到“锟斤拷”、“烫烫烫”,这是什么鬼?

以上这些问题都涉及到计算机中*础的知识点——字符集及字符编码的概念,本篇将从前端开发人员的视解,让你彻底搞清并理解这些问题的本质。

2、系列文章

本文是系列文章中的第5篇,本系列总目录如下:

《字符编码技术专题(一):快速理解ASCII、Unicode、GBK和UTF-8》

《字符编码技术专题(二):史诗级计算机字符编码知识入门,一文即懂!》

《字符编码技术专题(三):彻底搞懂字符乱码的本质,一篇就够!》

《字符编码技术专题(四):史上最通俗大小端字节序详解,一文即懂!》

《字符编码技术专题(五):前端必读的计算机字符编码知识入门》(*本文)

3、什么是字符集与字符编码

首先通过wiki中关于字符编码(Character_encoding)的定义来引入几个概念:

字符编码是将数字分配给图形字符的过程,特别是人类语言的书写字符,使它们能够使用计算机进行存储、传输和转换。组成字符编码的数值称为“码位”,它们共同组成“代码空间”、“代码页”或“字符映射”。

这里所说的代码页(CodePage)其实就可以理解为编码字符集(codedcharacterset),如Unicode、GBK字符集等。

简单来说:字符编码就是将字符映射为固定的码位值,存储在对应的编码字符集中。在不同的字符集中,同一个字符的码位不同。其中码位也有翻译成码点或者内码。

4、ASCII字符集

我们知道在计算机存储数据时要使用二进制进行表示。而最初计算机只在美国使用,因此人们要考虑如何使用二进制来表达52个英文字母(包括大小写)、阿拉伯数字(0-9)以及常用的符号(如!@#$等)。

于是便有从电报码发展而来的ASCII(AmericanStandardCodeforInformationInterchange,美国信息交换标准代码)(发音/ˈæski/)编码。它定义了英文字符和二进制的对应关系,一直沿用至今。

它采用了单字节编码方案(SBCS),一个字节的首位bit为0,用其余7个bit来表示128个字符(范围0x00-0x7F)。

具体是:

1)其中0-31和127(0x00-0x1F和0x7F)为控制字符,共33个。这些字符是不可见的,用于进行终端的换行、响铃、删除等动作;

2)32-126(0x20-0x7E)位可见字符,共95个,存储了空格、0-9十个阿拉伯数字、52个大小写英文字母,以及标点、运算符号等。

虽然现代英语使用128个字符就足够了,但表示其他语言就远远不够了。因此当ASCII进入欧洲后,又被扩展为了EASCII(ExtedASCII),将7bit扩展为8bit,并且前127个编码含义和ASCII保持一致。

但256个字符依旧无法解决众多使用拉丁字母的语言(主要是欧洲语言)问题。于是又扩展出了15个ISO8859字符集。

举几个字符集作为了解:

ISO/IEC8859-1(Latin-1)-西欧语言

ISO/IEC8859-2(Latin-2)-中欧语言

ISO/IEC8859-3(Latin-3)-南欧语言

ISO/IEC8859-4(Latin-4)-北欧语言

5、中文字符集5.1概述

前面讲到拉丁文所使用是ASCII和EASCII,但在亚洲——主要是中日韩(CJK)——光常用汉字就6000多个,而汉字总共有5、6万之多,单靠一个字节是远远没办法做到的。

因此聪明的中国人(也可能是日本人)就想到使用双字节编码(DBCS)来表示一个汉字,这样理论上2个字节可以表达65535个字符(当然很理论,实际上要少很多)。

这样就可以表示大部分常用字符了,接下来就要讲到和中文有关的GB系列(如GB2312、GBK、GB18030)的字符集了。

其实GB就是“国标”汉语拼音的首字母,而GBK就是“国标扩展”的意思,而GB18030是在保留GBK编码*础上再度扩展为可变的4字节编码空间的编码规范。

这里我们着重介绍下GB2312的编码结构。

5.2GB/T2312

GB2312全称《信息交换用汉字编码字符集·*本集》,收录了6763个汉字,682个拉丁、希腊、日文假名等字符。就将其中汉字覆盖了大陆99.75%的使用频率,足够大部分的场景使用的。

同时还把ASCII中标点符号、阿拉伯数字、英文字符用双字节收录在内,这里为了区别于ASCII中的字符,就将其做成了与汉字等宽的正方型效果(即拉丁字母的两倍宽),以表示和编码存储方式一一对应。因为字符编码和字宽的对应关系,我们这就称这类字符为「全角」字符,而ASCII中的字符则为「半角」字符。(这种叫法源于日本,因为在日本中“角”有“方块”的意思)

接下来看看如何编码的。规范将收录的汉字分成94个区,每个区又包含94个汉字,用所在的区位来表示一个字符(这种方法也称为区位码)。每个字符用2个字节表示,其中第一个字节称为「高位字节」表示分区号,第二个字节称为「低位字节」表示区段内的码位。另外实际GB2312仅用了87个区,88-94区保留待扩展。

为了避开ASCII中前面的31个不可见控制符和空格,在区位码*础上+32(0x20),这样获得的编码就称为ISO-2022国标码。

但是实际使用时,英文字符和汉字混用的情况很常见,国标码仅避让开了控制字符,依然和ASCII的英文字符有重合,因此决定在国标码的*础上将单字节的最高位bit存为1,即在国标码上+128(0x80),也就是区位码上+160(0xA0),这样就完美的避让开了ASCII字符区间。

这样种编码就叫做EUC-CN机内码:

而目前GB2312所采用的就是EUC这种主流编码方式,以便于兼容ASCII码。同时也可以根据最高位bit来判断是读取1个字符(ASCII)还是2个字符来进行解析。在GB2312内,高位字节范围0xA1-0xF7(01-87区+160或0xA0),低位字节范围0xA1-0xFE(01-94+160或0xA0)。

以“节”为例:

1)区位码是29-58;

2)EUC编码就是29+160,58+160=189,218;

3)十六进制就是0xBD,0xDA。

5.3GBK/GB18030

但是新的问题来了,GB2312仅收入了6763个常用的汉字,一些在标准推出以后的简化字(如啰)、港澳台使用的的繁体字、某些领导人的名字(***的*),以及各地区户籍中用到奇奇怪怪的名字、古籍中的汉字都没法正确表示。

因此在GB2312的*础上又扩展出了GBK和GB18030,并且向下兼容(严格来讲GB18030完全兼容GB2312,*本兼容GBK)。

GBK的*本原理就是继续扩展了GB2312中未使用的双字节空间,字符多达23940个。而GB18030则更为激进,干脆将存储空间变成可变的1、2、4字节,理论上可以存储161万个字符,完全涵盖Unicode范围。同时GB18030还收录了中日韩、繁体字、少数民族文字等多种语言。

5.4Big5

于此同时,海峡对岸的程序员也发明出了一套自己的繁体中文标准,由于起初项目名称是「五大中文套装软件」,因此称为Big5,也叫大五码。记得当年想要玩台湾“流入”的游戏,MagicWin这类的转码软件是装机必备。

6、Unicode字符集

随着世界各个地区编码方式越来越多,给不同地区间数据传输造成非常大的困难,比如一个从中国发送到日本邮件的,在不知道其编码的情况下,就会出现乱码。

因此终于有人开始想用一种更加通用的方案,来涵盖世界上所有的字符和符号——这就是Unicode,如其名字本身的含义一样。

首先Unicode也采用了16位的编码空间,即2个字节表示一个字符,用"U+"接4个十六进制数字表示(例如U+4AE0),每个字符分配一个唯一的「码点」(CodePoint,也称码位)。同时又定义了17个编组,每组称为一个「平面」(Plane)。这样字符范围从0x00000-0x10FFFF,这样就可以最多表示17*65536也就是100多万个字符,完全可以存储全世界所有的语言和符号了。

这其中将常用的字符存放在第一个编组,即0号平面(Plane0),也称之为「*本多文种平面」(BMP-BasicMultilingualPlane),其范围是0x0000-0xFFFF。而1-16号平面被称为「辅助平面」(SupplementaryPlanes),用于存储很少使用的文字或者图形符号(emoji表情符号就存于这些平面),其范围是0x10000-0x10FFFF。

Unicode字符表的范围涵盖的字符和符号非常广,不仅收录了表情符号、麻将、音乐符号等表意符号外,还收录了一些早已不再使用的文字,比如甲骨文、古埃及的圣书体,甚至收录了为影视剧而创造的语言——克林贡语。并且还在持续更新中,截止此文完成,最近的一次更新是在2022年9月。

但是这里有个比较严重的问题:

1)不同码位的字符使用不同的字符长度,但计算机无法知道到底是按照2个还是4个字符解析;

2)即便只使用英文字符前一个字节也必须是0,严重浪费了空间。

由于这个问题导致Unicode的在初期完全没法推广,直到互联网的普及,急需一种对Unicode字符的编码方式出现,这就是****UTF(UnicodeTransformationFormat)。在UTF中又出现了UTF-8、UTF-16、UTF-32这些不同的编码方式。

7、UTF-32

首先,人们想到的就是干脆所有字符直接用4个字节存储,不足4位的就补0代替。但是这样简单粗暴,尤其仅使用英文的数据,会造成了空间的极大浪费。

比如拉丁字母A的Unicode码点为U+0065,十六进制为0x41,如果使用UTF-32编码就是0x00000041。

于是人们又开始设计一种可以节省空间的编码方式——UTF-8。

8、UTF-8

UTF-8最大的特点是可变长编码,它使用1-4个字节表示一个字符。

它的编码方式如下:

1)由于Unicode在0+0000-0+007F范围和ASCII完全相同,因此使用单字节表示(这样ASCII和UTF-8的编码一样,是完全兼容的)。

2)在大于0+007F的字符时,是由1个前导字节(leadingbytes)和n个(n=1)尾字节(trailingbytes)的多字节结构组成。前导字节从最左侧起用1来表示这个字符有多少位,直到遇到0为止。尾字节前两位都是10,其余的bit位就是可用编码空间。

比如:

1)第一个字节是0xxxxxxx,就表示该字符只有1个字节;

2)第一个字节是110xxxxx,表示该字符有2个字节,第二个字节是10xxxxx;

3)第一个字符是1110xxxx,表示该字符有3个字节,后面字节分别是10xxxxx10xxxxx;

4)第一个字节是11110xxx,表示该字节有4个字节,后面字节分别是10xxxxx10xxxxx10xxxxx。

这里x就表示可用的编码空间,这也就是UTF-8中8表示至少8位表示一个字符,同时也是以8位为一组实现可变字节的编码方式。(这里之所以跳过了10xxxxxx,是因为10表示尾字节)

以下就是完整编码方式,当然5、6字节的编码方式不会出现,因为已经远超Unicode最大码点范围U+10FFFF了。

兔:

1)码点为U+5154;

2)二进制表示为101000101010100;

3)从末尾按6个bit分组101000101010100;

4)需要3个字节,则开头插入1110,中间不足8位用0补足,剩余前面插入10,则得到111001011000010110010100;

5)转为十六进制,E58594。

的emoji:

1)码点为U+1F430;

2)二进制为11111010000110000;

3)从末尾按6个bit分组11111010000110000;

4)第一部分为5位,插入1110位后占9位,超出一个字节,所以前面补一个字节,用4个字节表示。不足8位的用0补足。则得到11110000100111111001000010110000;

5)转为十六进制,F09F90B0;

由于JS中encodeURI是按照UTF-8进行百分号编码,因此感兴趣的同学可以进行验证下(代码如下所示)。

除了相对于4字节编码更省空间外,UTF-8编码还有以下比较优秀的特性。

1)ASCII是UTF-8的一个子集,一个纯ASCII字符串也是一个合法的UTF-8字符串。因此现存的ASCII数据可以不经修改即可使用。

2)单字节范围0x00-0x7F,而多字节的前导字节范围总是在0xC0-0xFD,尾字节都在0x80-0xBF中,三者完全没有重叠。通过范围就可以确定字节的类型,如果字节流在传输时中断,不完整的字节也不会被解码。这样数据有损坏、丢失,影响的范围也会很小。试想下,如果有一个按照每4个字节为一组的编码方法,如果丢失了第一个字节的数据,那么继续按照4字节一组的方式读取数据,后面的数据都将无法读取。

3)另外由于前导字节和尾字节范围互不重叠,即便从一个字符的某个中间字节开始读取,也不会错把它和下一个字符拼接错。这种特性也叫自同步码。比如,“兔子”的编码为E58594/E5AD90,你不会错把8594E5或者AD90当做一个字符,因为根本不存在这样规则的UTF-8编码。

4)仅通过首字节就能确定整体字节长度,而无需等待下一个字节的读取。

5)理论6个字节可以存放2^31个字符,即21亿个字符!即便是4字节也可以存放200多万个字符。其范围已超Unicode的容量了。

6)UTF-8中未使用0xFE和0xFF。

7)UTF-8字节串的排列顺序是固定的,和系统无关。没有字节序(即是按大端序还是小端序的先后顺序)的问题。因此也无需使用BOM头(虽然其BOM头是0xEF0xBB0xBF,但仅表示UTF-8编码格式,不表示字节顺序)。

虽然UTF-8有诸多优势,但是也有缺点:

1)中日韩文字至少需要3个字节存储,比GBK这类双字节存储要多一个字节;

2)由于其是变长字节,一个字符可能是1、2、3个字节,因此当计算一组字节中包含多少个字符,就要从头遍历,才能确定到底有多少个字符。当我们需要获取指定位置的字符时,依然要从头遍历。这也为程序的实现带来了很大的麻烦。

因此:一些程序在内部处理字符串时,就使用了另外一种编码方式UTF-16,其中JavaScript、Python就使用了这种编码方式来存储字符串。

9、UTF-16

UTF-16也采用了变长的编码方式。使用2个或者4个字节表示一个字符。

其规则是:

1)在*本平面BMP内(U+0000至U+FFFF)的字符用2个字节表示;

2)在辅助平面内(U+10000至U+10FFFF)的字符用4个字节表示。

其中BMP的字符直接使用Unicode码点对应即可,比如“兔”的码点U+5154,UTF-16编码就是0x5154。

可是BMP外,要如何使用4个字节表示呢?假如我们使用UTF-32的方式,直接使用码点映射会遇到什么问题?

还以“”为例,其码点是U+1F430。如果直接映射为0x0001F430,头2个字节0x0001对应的字符在BMP中已经存在,这样在读取数据时就无法区分到底是按照2字节解析,还是按照4个字节解析了。

幸而在BMP中,从U+D800到U+DFFF是一个永远保留不做映射的空段,于是UTF-16将辅助平面内的字符——共需要20个bit表示(由0x10FFFF-0x100000=0xFFFFF计算得来)——拆分成2段,前10个bit位映射到U+D800-U+DBFF(正好1024个),后10个bit映射到0xDC00-0xDFFF(也正好1024个)。这样刚好就可以在范围互不重复的情况下,用4个字节表示一个字符了。

这种用4个字节表示的方式,就被称作「代理对」(SurrogatePair),高位的10bit被称为「前导代理」(leadsurrogates),低位10bit被称为「后尾代理」(trailsurrogates)。

其具体的算法如下:

1)BMP内字符,直接使用码点作为对应的字节,长度为2个字节;

2)辅助面内的字符:

a.码位减去0x10000;

b.高位的10bit的值加上0xD800,得到前导代理;

c.低位的10bit的值加上0xDC00,得到后尾代理。

用公式计算如下:

我们依旧使用“”来举例,看下UTF-16是如何进行编码的。

1)码点为U+1F430;

2)减去0x10000,为0xF430;

3)二进制为1111010000110000;

4)前10bit111101,十六进制0x3D。后10bit0000110000,十六进制0x30;

5)分别加上0xD800和0xDC00,得到0xD83D和0xDC30;

6)最终得到D83DDC30。

JS中escape方法(已不推荐,这里仅验证用)返回的就是UTF-16编码,可以进行验证:

同时ES6中支持用Unicode码点和UTF-16编码表示一个字符,也可验证:

由于前导代理、后尾代理、BMP中的有效字符的码位,三者互不重叠,因此在检查字符时,可以很容易的确定字符的边界。这也意味着UTF-16也是一种自同步的编码方式,这点和UTF-8是一样的。

UTF-16相对于UTF-8更容易进行随机访问和索引,是因为UTF-16中每个字符都使用固定的2个或4个字节表示,因此可以通过简单的数学运算来快速计算出每个字符的位置。而UTF-8需要解析整个数据流才能确定每个字符的位置,这使得UTF-8在进行随机访问和索引时比UTF-16更加复杂和耗时。

10、UCS-2

在谈及UTF-16时,通常会涉及到UCS-2的概念,这里着重讲下两者的区别。

UCS-2是UniversalCharacterSetcodedin2octets的简称,由于1989年UCS标准发布时,只有Unicode*本面字符,因此使用2个字节就可以表示一个字符了。

而1996年UTF-16发布时,已经扩展出了辅助平面,可使用代理对方式表示辅助平面的字符了,因此UTF-16已明确表示是UCS-2的超集。

所以目前UCS-2已经是过时的叫法了,UTF-16已经取代了UCS-2的概念。

如果某个程序说自己支持UCS-2可能就意味着仅支持BMP内的字符集。

11、大端序和小端序

将字符转换为UTF-16(或UTF-32)后,在使用时需要读取并存储在内存中。以“乙”字为例,转成UTF-16后,编码为0x4E59,需要用2个字节来存储——0x4E0x59。

如果4E在前,59在后,我们就称作为大端序(Big-ian):

反之59在前,4E在后,就称之为小端序(Little-ian):

之所以会有这样不同的存储方式,是因为有的系统或者处理器,在处理多字节数据时,会从低位内存地址到高位内存地址的顺序读取数据,因此为了保证读取后数据的正确,就采用了不同字节序(ianness)的方式。

为了标识一段数据的字节序,通常使用字节顺序标记(Byte-OrderMark,BOM)来进行标识。通常BOM会出现在文件、字节流的头部,用来标识接下来数据的字节顺序。

在UTF-16,0xFEFF表示大端序,0xFFFE表示小端序。

我们来验证下,使用VSCode安装HexEditor插件,然后新建一个空白文件,输入“乙”和“”,然后分别使用两种编序,通过HexEditor查看。

UTF-16BE:

UTF-16LE:

另外UTF-8也存在BOM头,值为0xEF0xBB0xBF。但它只用来标识文件的编码方式,而不用来说明字节顺序。因为UTF-8本身有着固定的编码顺序,字节序对于UTF-8来说毫无意义。

在实际使用中,也应该尽量避免带BOM头的UTF-8,因为BOM头可能会影响一些程序的解析器(如Unix下的Shebang)的处理。

另外由于UTF-8是一种稀疏的编码,很大一部分可能的字符组合不会产生有效的UTF-8文本,因此许多程序的在解析UTF-8文件时,使用启发式的分析方法可以很有把握地检测出文件是否使用UTF-8,而无需加入BOM。

PS:关于大小端字节序问题,可以详读另一篇《史上最通俗大小端字节序详解,一文即懂!》。

12、锟斤拷烫烫烫12.1概述

“锟斤拷”和“烫烫烫”恐怕是程序界中最经典的故事(事故)之一了,了解了前面的编码知识,就会很容易理解它的由来了。

12.2锟斤拷

由于Unicode字符集在不断更新中,因此会出现A系统发送的字符,在B系统中无法识别的情况。

于是Unicode规定对于无法识别的字符,一律使用�(0xFFFD)(Replacementcharacter)字符来代替。

而0xFFFD在UTF-8编码下为0xEF0xBF0xBD,当多�出现时,就会产生连续的0xEF0xBF0xBD0xEF0xBF0xBD。

如果这些字符又被使用了GB编码的程序中打开,就会按照GB双字节编码将其解析。这样刚好就对应了「0xEFBF锟」、「0xBDEF斤」、「0xBFBD拷」这几个字。

12.3烫烫烫

C\C++编译器在debug模式下,引入的一种内存保护机制,会给特定的内存赋一个特定的初值。其中未被初始化的栈内存,会被写入0XCC。当连续的0xCCCC在GB编码下就是「0xCCCC烫」了。

当然一千人眼中有一千个哈姆雷特,程序员也是如此。比如台湾是“嚙踝蕭”,而日本是“フフフフフフ”了。

13、JavaScript与Unicode13.1薛定谔的length

前面我们提到JavaScript中''.length=2,要说明这个问题之前,先简单介绍下JavaScript与编码的历史。

1990年UCS-2编码发布,1995年JavaScript诞生,第一个JS解释器被使用,1996年UTF-16发布。

从时间线上来看JavaScript解释器只能采用了UCS-2的编码方式。而length统计的是代码单元而非真正的字符数量,因此按照UCS-2的编码方式,每个字符由2个字节构成,即两个2字节组成字符的length=1。

而是辅助平面的字符,其UTF-16的编码为D83DDE01,因此JavaScript会把它当做0xD83D和0xDE01两个字符。这也就是length=2的原因。

除length以外,JavaScript中和字符串处理相关的函数,大都存在这类问题。

如:

13.2ES6的字符处理

但在ES6中,大大增强了对Unicode的支持,提供了新的方法解决了以上问题。

为了保持兼容,length属性还是原来的行为方式。为了得到字符串的正确长度,可以采用如下的方式:

原Unicode表示字符法,仅支持\uxxxx的表示,仅支持\u0000-\uffff范围。如:“兔”也可以用\u5154表示:

而超出\uffff的部分需要使用双字节方式表示,如:“”需要用\uD83D\uDC30表示,而不能用码位值\u1F430表示:

而在ES6中只要将码点放入{}中,即可正确表示其含义:

ES6新增了几个专门处理4字节码点的函数,如:

1)():从Unicode码点返回对应字符;

2)():从字符返回对应的码点;

3)():返回字符串给定位置的字符。

ES6正则提供了u修饰符,对正则表达式添加4字节码点的支持:

13.3length依旧不准的合成字符

上面解释了为什么length=2,是否意味着length非1即2呢?别急,我们运行下''.length。

居然length=11,为什么整整齐齐的一家人要这么长?这到底发生了什么?

要解释这个问题,就要讲到Unicode的合成字符(combiningcharacter)。

在一些语言中,会在字符的上面添加一些符号,以表示不同的发音或表示不同的含义。例如:汉语拼音有ā的音标、ü上面的两点,法语和西班牙语中的é表示重音,越南国语字中的Ô表示一个字母。

而在Unicode中有两种表示方法:

1)使用一个独立的码点,例如Ô的码点为U+00D4;

字符编码技术专题(五):前端必读的计算机字符编码知识入门

2)使用*本字符+**附加符号**组合的方式(如O的码点是U+004F,扬抑符ˆ的码点是U+0302,组合后同样展示Ô,其表意与第一种相同)。

这两种方式表示虽然在视觉和语义上含义相同,但是在js中却无法理解:

因此,在ES6中提供了
(),用来将字符的不同表示方法统一为同样的形式,称为Unicode正规化(该方法有局限性,后文会讲)。

这类表情也被称为EmojiZWJSequences:

而的length分别为2,而零宽连字符的length为1,因此最终length就是24+31=11。

通过下面的方式也可以验证其组合:

当然这里想通过normalize()判断是否等价是不行的,首先没有独立的码点,其次该方法目前不能识别三个或三个以上字符的合成。

13.4合成字符的大麻烦

上面提到关于Unicode的问题不仅限于JavaScript,在Python、Java等语言中也会遇到。通常这类合成符号在实际开发中会造成很多棘手的问题。

13.4.1)input的maxlength:

在设置input的maxLength属性时,就会遇到不同浏览器的差异。

Chrome将粘贴后,会按照进行裁剪,多出的字符会被裁剪掉:

例子在这里:Safari(+Moblie)则会按照实际字符个数计算:

在华为默认的浏览器测试,行为和Chrome的相同:

该问题在开发多语言业务中,可能会更加明显(如后台的字符数限制、前端展示等)。如果希望正确计算需要使用三方库,例如:
(抛砖引玉,未验证覆盖情况)。

13.4.2)带音标的文字:

在一些场景下,会有多个附加音标修饰一个字符的情况。例如'ẹ́'是由字母“e”\u0065、尖音符“◌́”****\u0301、下句点****“◌̣”\u0323****组成。其中后两个音标的顺序可互换,甚至也可以是'ẹ'+'́'或者'é'+'̣'的组合。

这时要使用正则匹配字符变得异常复杂。

虽然Unicode属性转义表达式(Unicodepropertyescapes),但可惜的是ES2018以前的版本并不支持,因此可以考虑使用XRegExp来实现。

其中\pL和\pM的含义如下:

1)\p{L}or\p{Letter}:anykindofletterfromanylanguage,匹配一个字母;

2)\p{M}or\p{Mark}:acharacterintedtobecombinedwithanothercharacter(,umlauts,enclosingboxes,etc.).匹配附加符号。

虽然在ES2018中引入了Unicode属性转义符,但在浏览端上依然要考虑使用XRegExp来实现,当然可以考虑在服务端处理,因为、、、.、Go10等已经支持这些表达式了,当然不同语言的支持程度可能略有不同。

13.5数据库中存储表情

另外在数据库存储时使用了不同的表示方式存储同一含义的字符,会导致检索失败。MySQL存储时也要考虑使用utf8mb4_unicode编码格式,才能正确存储表情符。

14、本文小结

最后关于Unicode字符编码模型,可以概况为四个层级。

1)抽象字符库(ACR,AbstractCharacterRepertoire):即要编码的字符,比如某些字母表和符号集。这一层级会将语言中的字符进行抽象。

2)编码字符集(CCS,CodedCharacterSet):从抽象字符库到一组非负整数(即代码点)的映射。这一层级可以理解为字符到Unicode码点映射的过程。

3)字符编码形式(CEF,CharacterEncodingForm):从代码点到特定宽度(比如32-bit整数)的代码单元序列的映射。这一层级理解为UTF-8、UTF-16、UTF-32的编码过程。

4)字符编码方案(CES,CharacterEncodingScheme):从代码单元序列到字节序列的映射。这一层级就涉及到了如大端序、小端序的设计和存取过程。

附录:更多IM技术精华文章

[1]新手入门一篇就够:从零开发移动端IM》

[2]零*础IM开发入门(一):什么是IM系统?》

[3]零*础IM开发入门(二):什么是IM系统的实时性?》

[4]零*础IM开发入门(三):什么是IM系统的可靠性?》

[5]零*础IM开发入门(四):什么是IM系统的消息时序一致性?》

[6]移动端IM开发者必读(一):通俗易懂,理解移动网络的“弱”和“慢”》

[7]移动端IM开发者必读(二):史上最全移动弱网络优化方法总结》

[8]从客户端的角度来谈谈移动端IM的消息可靠性和送达机制》

[9]现代移动端网络短连接的优化手段总结:请求速度、弱网适应、安全保障》

[10]史上最通俗Netty框架入门长文:*本介绍、环境搭建、动手实战

[11]强列建议将Protobuf作为你的即时通讯应用数据传输格式

[12]IM通讯协议专题学习(一):Protobuf从入门到精通,一篇就够!

[13]微信新一代通信安全解决方案:*于的MMTLS详解

[14]探讨组合加密算法在IM中的应用

[15]从客户端的角度来谈谈移动端IM的消息可靠性和送达机制

[16]IM消息送达保证机制实现(一):保证在线实时消息的可靠投递

[17]理解IM消息“可靠性”和“一致性”问题,以及解决方案探讨

[18]融云技术分享:全面揭秘亿级IM消息的可靠投递机制

[19]IM群聊消息如此复杂,如何保证不丢不重?

[20]零*础IM开发入门(四):什么是IM系统的消息时序一致性?

[21]一套亿级用户的IM架构技术干货(下篇):可靠性、有序性、弱网优化等

[22]如何保证IM实时消息的“时序性”与“一致性”?

[23]阿里IM技术分享(六):闲鱼亿级IM消息系统的离线推送到达率优化

[24]微信的海量IM聊天消息序列号生成实践(算法原理篇)

[25]社交软件红包技术解密(一):全面解密QQ红包技术方案——架构、技术实现等

[26]网易云信技术分享:IM中的万人群聊技术方案实践总结

[27]企业微信的IM架构设计揭秘:消息模型、万人群、已读回执、消息撤回等

[28]融云IM技术分享:万人群聊消息投递方案的思考和实践

[29]为何*于TCP协议的移动端IM仍然需要心跳保活机制?

[30]一文读懂即时通讯应用中的网络心跳包机制:作用、原理、实现思路等

[32]融云技术分享:融云安卓端IM产品的网络链路保活技术实践

[33]阿里IM技术分享(九):深度揭密RocketMQ在钉钉IM系统中的应用实践

[34]彻底搞懂TCP协议层的KeepAlive保活机制

[35]深度解密钉钉即时消息服务DTIM的技术设计

[36]*于实践:一套百万消息量小规模IM系统技术要点总结

[37]跟着源码学IM(十):*于Netty,搭建高性能IM集群(含技术思路+源码)

[38]一套十万级TPS的IM综合消息系统的架构实践与思考

(本文已同步发布于:

不容错过