目标
了解字符集的来源
了解emoji的来源
了解emoji的字符编码
常见字符集问题
字符集的来源
所谓字符集(Character Set)简而言之就是字符(Character)的集合,常见的字符有数字、字母、标点符号、功能符号等;字符又是由字节(Byte)组成,字节是计算机信息技术用于计量存储容量的一种计量单位,也表示一些计算机编程语言中的数据类型和语言字符,一个字节存储8位无符号数,储存的数值范围为0-255。这里的【位】指的是二进制数字0或1。字节通常简写为【B】,而位通常简写为小写【b】,计算机存储器的大小通常用字节来表示。总结来说,位组成字符(通常是8位,但是其他系统可能是6位或者其他位),字符组成字符集。
明白了了字符集的含义,我们需要了解一下字符集的历史。因为计算机只识别0和1,但是对于人类来说0和1理解比较麻烦,人们常用的是自然语言,此时有些人考虑是否可以把自然语言转化为机器语言,使用时输入自然语言但是计算机运行时有转化为0或1,这样编译器就诞生了。程序员常用的编程语言通常是高级语言,编译器把高级语言编译成汇编语言,再把汇编语言转化为机器语言。举个例子,我们键盘输入的字符通产会被编译成0和1组成的字节存储到计算机中,而屏幕显示时计算就有会把0和1转化为原来输入的字符。在使用计算机的过程中,发现同样系统的计算机字符通常转化为固定二进制编码,这些字符和二进制编码的关系被整理出来,形成类最初的字符和二进制编码的映射表,即字符集;字符集就是字符和二级制编码的映射关系表。
早期因为不同的计算机系统的字符集不同,为了统一字符集,制定了最早的一版字符集标准ASCII(American Standard Code for Information Interchange,美国信息互换标准编码),这是基于罗马字母表的一套电脑编码系统,这个字符集是有7位表示一个字符,从0到127一共128个字符。后面发现128个字符不够使用,又对字符进行了扩展,使用8位表示一个字符,这样可以使用0到255共256个字符。这里需要注意的是这个字符集不包含中文,只有西方常用的字母、数字、常用符号等。中文的字符集主要有GB2312、BIG5、GBK、GB18030。GB2312是中国大陆最早的中文字符集标准,使用双字节(16位)表示中文字符。BIG5是中国台湾地区的中文字符集标准,也是使用双字节(16位)表示中文字符,但是内容要比GB2312要多,里面有繁体字符,GB2312是没有繁体字符的。GB18030是2000年中国政府退出的最新中文字符集标准,基本包含了ASCII、GB2312、BIG5的内容,采用单字节(8位),双字节(16位),四字节(32位)三种方式表示字符,其中单字节表示ASCII、双字节表示中文字符、四字节表示扩展字符,这个字符集标准在东亚地区广泛使用。GBK(Chinese Internal Code Specification,汉字内码扩展规范,GBK即“国标”、“扩展”汉语拼音的第一个字母)是GB18030的前身,GB2312使用过程中发现字符不够用,就对GB2312进行了扩展,后面逐渐形成了GB18030字符集标准。最后是Unicode(Universal Multiple-Octet Coded Character Set,简称万国码)字符集编码标准,如果把各种文字编码形容为各地的方言,那么Unicode就是世界各国合作开发的一种语言。UTF-8是Unicode的其中一个使用方式。 UTF是 Unicode Tranformation Format,即把Unicode转做某种格式的意思。UTF-8使用可变长度字节来储存 Unicode字符,例如ASCII字母继续使用1字节储存,重音文字、希腊字母或西里尔字母等使用2字节来储存,而常用的汉字就要使用3字节。辅助平面字符则使用4字节。UTF-32、UTF-16和 UTF-8 是 Unicode 标准的编码字符集的字符编码方案,UTF-16 使用一个或两个未分配的 16 位代码单元的序列对 Unicode 代码点进行编码;UTF-32 即将每一个 Unicode 代码点表示为相同值的 32 位整数。我们常用的就是UTF-8、GBK、ASCII、GB18030等字符集,这些字符集在我们编程是经常会用到,需要熟练掌握,下面这张表格列举了字符集的内容和特点。
字符集编码
包含内容
存储方式
ASCII
ASCII基础字符集:控制字符、回车键、退格、换行键等。可显示字符:英文大小写字符、阿拉伯数字和西文符号。
ASCII扩展字符集:表格符号、计算符号、希腊字母和特殊的拉丁符号。
ASCII基础字符集:7位(bits)表示一个字符,共128字符,字符值从0到127。
ASCII扩展字符集:使用8位(bits)表示一个字符,共256字符,字符值从0到255。
GB2312
简化汉字及一般符号、序号、数字、拉丁字母、日文假名、希腊字母、俄文字母、汉语拼音符号、汉语注音字母,共 7445 个图形字符。其中包括6763个汉字,其中一级汉字3755个,二级汉字3008个;包括拉丁字母、希腊字母、日文平假名及片假名字母、俄语西里尔字母在内的682个全角字符。
使用了双字节储存方法,两个字节中前面的字节为第一字节,后面的字节为第二字节。习惯上称第一字节为“高字节” ,而称第二字节为“低字节”。
“高位字节”使用了0xA1-0xF7(把01-87区的区号加上0xA0),“低位字节”使用了0xA1-0xFE(把01-94加上0xA0)。
Big5
收录13,053个中文字,该字符集在中国台湾使用。耐人寻味的是该字符集重复地收录了两个相同的字:“兀”(0xA461及0xC94A)、“嗀”(0xDCD1及0xDDFC)。没有考虑社会上流通的人名、地名用字、方言用字、化学及生物科等用字,没有包含日文平假名及片假名字母。
使用了双字节储存方法,以两个字节来编码一个字。第一个字节称为“高位字节”,第二个字节称为“低位字节”。高位字节的编码范围0xA1-0xF9,低位字节的编码范围0x40-0x7E及0xA1-0xFE。
GBK
是在GB2312-80标准基础上的内码扩展规范,其编码范围从8140至FEFE(剔除xx7F),共23940个码位,共收录了21003个汉字,完全兼容GB2312-80标准,支持国际标准ISO/IEC10646-1和国家标准GB13000-1中的全部中日韩汉字,并包含了BIG5编码中的所有汉字。
使用了双字节编码方案。
GB 18030
单字节部分使用0×00至0×7F码(对应于ASCII码的相应码)。双字节部分收录内容主要包括GB13000.1全部CJK汉字20902个、有关标点符号、表意文字描述符13个、增补的汉字和部首/构件80个、双字节编码的欧元符号等。 四字节部分收录了上述双字节字符之外的,包括CJK统一汉字扩充A在内的GB 13000.1中的全部字符。
采用单字节、双字节和四字节三种方式对字符编码。单字节部分使用0×00至0×7F码(对应于ASCII码的相应码)。双字节部分,首字节码从0×81至0×FE,尾字节码位分别是0×40至0×7E和0×80至0×FE。四字节部分采用GB/T 11383未采用的0×30到0×39作为对双字节编码扩充的后缀,这样扩充的四字节编码,其范围为0×81308130到0×FE39FE39。其中第一、三个字节编码码位均为0×81至0×FE,第二、四个字节编码码位均为0×30至0×39。
UTF-8
一个US-ASCIl字符只需1字节编码(Unicode范围由U+0000~U+007F)。
·带有变音符号的拉丁文、希腊文、西里尔字母、亚美尼亚语、希伯来文、阿拉伯文、叙利亚文等字母则需要2字节编码(Unicode范围由U+0080~U+07FF)。
·其他语言的字符(包括中日韩文字、东南亚文字、中东文字等)包含了大部分常用字,使用3字节编码。
·其他极少使用的语言字符使用4字节编码。
UTF-8使用1~4字节为每个字符编码:
·一个US-ASCIl字符只需1字节编码(Unicode范围由U+0000~U+007F)。
·带有变音符号的拉丁文、希腊文、西里尔字母、亚美尼亚语、希伯来文、阿拉伯文、叙利亚文等字母则需要2字节编码(Unicode范围由U+0080~U+07FF)。
·其他语言的字符(包括中日韩文字、东南亚文字、中东文字等)包含了大部分常用字,使用3字节编码。
·其他极少使用的语言字符使用4字节编码。
emoji来源
下面按照时间线看一下emoji的历程
时间线
事件
1982.9.19
美国卡耐基·梅隆大学的法尔曼教授在电子公告板上输出这样一串字符:😃 。随后,为了区分正儿八经的消息和善意的玩笑,他又添加了:-(用以区别。
这被认为是人类历史上第一个真正意义上的表情,也是笑脸符号的由来。自那以后,凡是在支持 ASCII 系统的电脑上,人们都可以用:-)、 : -o、: -b等符号来表达某种情绪。
1995
日本运营商NTT推出了一款新传呼机,可以在发简讯的同时配带不同的面部表情符号,比如爱心等。这款新机型刚上市便获得年轻人的青睐,占据近40%的市场份额。而后,NTT才意识到,让年轻人如此着迷的,就是这些表情符号。
他们干脆放手一搏,在平台上开发出最早的 emoji。其负责人表示,这些12x12像素的表情包,要将人类所有的表情都包含在内。
emoji是一个日语词,e表示"絵",moji表示"文字"。连在一起,就是"絵文字"。
2010
Unicode 开始为 Emoji 分配码点。也就是说,现在的 Emoji 符号就是一个文字,它会被渲染为图形。
2011
emoji最先出现于苹果在2011年6月发布的iOS 5.0系统,由其实习生Willem Van Lancker于2009年所画。苹果为进入日本市场与运营商软银合作,应软银要求Willem Van Lancker画了500个几乎涵盖所有日常的emoji 。最初,苹果版emoji只在日本使用。
2014
牛津词典在线版正式收录了emoji 这一词条。
2015
Emoji 的国际标准在 2015 年出台
Emoji 1.0:2015年8月
Emoji 2.0:2015年11月
Emoji 3.0:2016年6月
Emoji 4.0:2016年11月
Emoji 5.0 (beta):2017年3月
了解emoji的字符编码
emoji和Unicode的关系
emoji本身也是字符。unicode字符集编码标准吸收了emoji,UTF-8等字符集编码也给emoji添加了字符编码,注意unicode只是定义emoji的字符编码和含义,但是并没有定义如何显示字符,具体字符样式实际上是根据系统自己实现的。可以去这个网站查看emoji在不同系统的显示。
还有一点需要注意,emoji最终都是由软件展示的,而软件离不开编程语言,很多编程语言的内部编码用的都是UTF-16,例如Java、JavaScript、c#、python等,emoji的存储其实依赖于当前的编码方式。
UTF-16
这里先讲几个概念,方便理解后面的内容
码点(Code Point):Unicode编码对应的二进制数字,每个码点在字符编码中对应唯一一个字符。码点只规定了一个字符对应的数值,并没有规定这个数值如何存储,视编码方案不同有不同的存储方式。
码元(Code Unit):码元是Unicode字符编码的基本单位,一个或多个码元组成一个码点。例如,UTF-8中最少一个字节存储一个字符,那么code unit就是8位大小。UTF-16中最少两个字节存储一个字符,那么code unit就是16位大小。类似,UTF-32中的code unit是32位大小。从中可以看出,UTF-后面的数字指的就是该方案下code unit的bit位数。
平面(Plane):Unicode的编码空间从U+0000到+10FFFF,共有1,112,064个码点,可用来映射字符. 整个编码空间可以划分为17个平面(plane),每个平面包含216(65,536)个码位。17个平面的码位可表示为从U+xx0000到U+xxFFFF,其中xx表示十六进制值从00到10,共计17个平面。第一个平面称为基本多语言平面(Basic Multilingual Plane, BMP),或称第零平面(Plane 0)。其他平面称为辅助平面(Supplementary Planes)。基本多语言平面内,从U+D800到U+DFFF之间的码位区段是永久保留不映射到Unicode字符,称为代理码点(Surrogate Code Point)。UTF-16就利用保留下来的0xD800-0xDFFF区段的码位来对辅助平面的字符的码位进行编码。还有一点需要注意,常用的字符、数字、汉字等码点基本上都在BMP。
由于Unicode 只是给每个字符规定了一个码点,是连续分配的,而没有考虑到一些其他的冲突问题,即前面说过的字符定界的问题。码点值越大,需要完整表示使用的二进制位数越多,假如直接把码位值转换成二进制存储,在 Unicode 中往后的字符可能就需要 3 个字节甚至4个字节来表示了。这就导致定界问题,如何确定到底用几个字节来表示一个字符呢?这是Unicode标准没有指明的。涉及到具体存储光看Unicode编码无法解决问题,如何存储还需要另外的方案。
容易想到,最简单的做法是让所有的字符类型都用四个字节定长存储,计算机在读取时统一读取四个字节作为一个码位来理解,这样就解决了定界问题。UTF-32采用的是这种方案,这样解决了编码问题,但是却造成了空间的极大浪费。大部分常用字符位于BMP内,本来两个字节就能存储,却统一变成了四个字节来存储,使得存储空间浪费了很多。
因此,目前采用广泛的Unicode编码存储方案都是变长的存储方案,如UTF-8和UTF-16。
UTF-8和UTF-16的具体方案,大家可以看参考资料的文章或者自行查询,这里重点说一下UTF-16的代理码点。
前面提到,在基本平面内,从 U+D800 到 U+DFFF 的码位是代理区,不对应任何字符。因此,UTF-16就用了这一段区域巧妙地解决了边界问题。
UTF-16将代理区进一步划分成两部分。0xD800~0xDBFF,称为高半代理(leading surrogate)。0xDC00~0xDFFF分区, 称为低半代理(trailing surrogate)。
不难得到,高半代理范围含有0xDBFF-0xD800+1=0x0400=210个码位,低半代理范围也有0x0400=210个码位。
除去基本平面已经分配存储方案的码位后,辅助平面还剩下0x10FFFF-0x010000+1 = 0x0F0000,约为2^20个码位。这些码位还需要分配存储方案。假如将这些码位值全部存储成二进制数,需要至少20位来存储,至少也需要四个字节。
UTF-16的做法是,将这四个字节的前两个字节映射到高半代理表示的范围,后两个字节映射到低半代理的范围。高半代理和低半代理总共能表示210*210=220个数,刚好能为每个辅助平面还剩下的220个码位分配存储方案。
这样,按两个字节两个字节读取的方式判别,假如读到的值不在代理区内,就证明这就是一个BMP内的字符。假如读到的值位于前导代理范围内,证明这是一个四字节辅助字符的开头,后面两个字节是这个字符的延续。假如读到的值位于后导代理范围内,证明前面两个字节也属于这个辅助字符的一部分。
emoji显示的规则
emoji的unicode编码存储基本上就是上面提到的方案,由于我们编程时需要处理emoji,如果没有特别声明编码模式,基本上都会直接采用UTF-16进行处理。上面提到了一个查看emoji的网站,我们查看字符时会发现这样的问题,有的emoji对应一个Unicode的编码,但是有的emoji对应多个Unicode,例如下图
emoji的Unicode
简单的说emoji分为两类(自定义,不代表官方):
简单类:即一个Unicode编码表示一个emoji。
组合类:即多个Unicode编码表示一个emoji。
组合类又分为多种类型:
emoji旗帜序列(Emoji Flag Sequence):国家的旗帜一般由两个Unicode字符组成,这两个字符一般是国家英文中两个字母的缩写组成。,例如 U+1F1E8 为地域指示符 C, U+1F1F3 为地域指示符 N。这些指示符两两组合表示一个国旗CN即中国国旗(🇨🇳),在不支持Emoji5.0的系统上,会被显示为两个字母Emoji表情(🇨 🇳)。
emoj国家示例
emoji标识序列(Emoji Tag Sequence):行政区域,一般由7个Unicode组成,第一个是简单旗帜的Unicode,第二个是表示修饰的Unicode,第三个表示文本修饰的Unicode,第4~6个是地区名字前三个字母标识Unicode,最后一个表示结束的Unicode。
emoj行政区域示例
emoji声明选择器(Emoji Presentation Selector):这种emoji的Unicode编码最大特点会在编码中添加一个U+FE0F编码,这个编码会修饰它之前的编码,例如下图中的457的emoji,U+FE0F会修饰U+2642,U+1F6B4表示骑自行车的人,U+2642表示男性,所以这个emoji表示骑自行车车的男性。
emoj声明选择器i示例
emoji键帽序列(Emoji KeyCap Sequence):这种emoji其实是emoji声明选择器的一种变种,这类 emoji 序列是将数字(0-9),* 与 # 通过一个 U+20E3 字符转换为键帽的样式。可以理解为它有两个修饰符,U+FE0F和 U+20E3, U+20E3本身就是表示键帽。例如下图
emoji键帽序列示例
emoji无缝连接序列(Emoji ZWJ Sequences):这种emoji可以将多个emoji拼接成emoji,两个emoji的Unicode编码之间会使用U+200D进行拼接。例如下图233,U+1F468表示男人,U+1F9B0表示红头发,拼接一起就是红色头发的男人。很多平台使用这种方式进行自定义emoji使用,而不需要等待Unicode标准发布。
emoji无缝连接序列示例
上面只是列举了常见的几种方式,实际上还有很多,具体的大家可以到Unicode官网查询。
常见字符集问题
常见emoji问题
如何过滤字符串的emoji?
目前有以下方案:
穷举法:使用正则过滤Unicode所有的组合。当前很多正则使用的就是这种方式,缺点很明显,实际上很难排除所有的emoji,因为emoji的Unicode编码更新后不在范围内且平台自定义emoji可能不匹配正则。优点是比较简单,代码比较少。
此方法还存在一个隐患,对于组合类emoji如果匹配的不对可能转化为另一个字符,例如
为什么会出现这种情况哪?下边看一下我的代码,
emoji转为16进制
很明显emoji在正则时会转化为16进制(这里可以明显看出emoji的编码是由代理码点组成的),然后正则匹配到前两个字符,但是没有匹配到后面的字符,此时字符剩余’200d’, ‘2642’, ‘fe0f’;换成Unicode码就是U+200D、U+2642、U+FEOF,因为U+200D只能链接emoji,此时前面没有emoji,实际上没有用,U+FEOF相当于修饰字符,没有实际意义,其实有意义的是U+2642,此时相当于U+2642,查询这个字符就是’♂️’
查询Unicode字符含义
目前比较全的正则表达式是
过滤字符串中的emoji
function filterEmojiOfString(emojiString = ‘’) {
return emojiString.replace(
/(?:[\u2700-\u27bf]|(?:\ud83c[\udde6-\uddff]){2}|[\ud800-\udbff][\udc00-\udfff]|[\u0023-\u0039]\ufe0f?\u20e3|\u3299|\u3297|\u303d|\u3030|\u24c2|\ud83c[\udd70-\udd71]|\ud83c[\udd7e-\udd7f]|\ud83c\udd8e|\ud83c[\udd91-\udd9a]|\ud83c[\udde6-\uddff]|\ud83c[\ude01-\ude02]|\ud83c\ude1a|\ud83c\ude2f|\ud83c[\ude32-\ude3a]|\ud83c[\ude50-\ude51]|\u203c|\u2049|[\u25aa-\u25ab]|\u25b6|\u25c0|[\u25fb-\u25fe]|\u00a9|\u00ae|\u2122|\u2139|\ud83c\udc04|[\u2600-\u26FF]|\u2b05|\u2b06|\u2b07|\u2b1b|\u2b1c|\u2b50|\u2b55|\u231a|\u231b|\u2328|\u23cf|[\u23e9-\u23f3]|[\u23f8-\u23fa]|\ud83c\udccf|\u2934|\u2935|[\u2190-\u21ff])/g, ‘’)
}
反向限定法:这个方法其实是一种反向思维,既然过滤emoji,那么是否通过只允许输入的内容来实现。一般不允许输入emoji,其实就是只允许输入字母、数字、符号、中文字符等,这些字符基本上都在BMP中。注意:如果正则中的Unicode范围不生效,例如\u3000-\u303f,检查“-”是否有问题,复制的“-”可能有问题。
只允许输入ASCII码+标点符号+中文的正则表达式
function filterEmojiOfString2(emojiString = ‘’) {
const reg = /[^(\u2000-\u206f|\u3000-\u303f|\uff00-\uffff|\u0000-\u00ff|\u4e00-\u9fcb|\u3400-\u4db5|\u2f00-\u2fd5|\u2e80-\u2ef3|\uf900-\ufad9|\ue815-\ue86f|\ue400-\ue5e8|\ue600-\ue6cf|\u31c0-\u31e3|\u2ff0-\u2ffb|\u3105-\u312f|\u31a0-\u31ba|\u3007)]+/gu
return emojiString.replace(reg, ‘’)
}
MySQL为什么不能存储emoji?
问题现象
当插入MySQL数据的字符串中包含emoji且此时MySQL的字符集是utf8时,数据库会爆出错误。
MySQL数据库插入emoji时报错
问题原因
MySQL的utf8字符集采用的是3字节存储,而emoji在Unicode中一般都是4字节存储,此时存储不了emoji。但是MySQL的utf8mb4字符集是4字节的,所以把MySQL的数据库、表、链接、配置等统一改为utf8mb4就可以了。
如何在不同平台显示相同的emoji?
通过上面的文章,大家应该清除,Unicode只是制定了emoji的编码和含义,具体实现取决于平台。如果想要不同平台实现相同效果,则需要使用同一套emoji进行匹配。目前没有找到对应的方案,但是有一个思路可以参考一下。设计一组emoji的图片,然后用户输入时,使用图片替换emoji,则存储的实际上都是图片,图片在不同平台显示的都是一样的,这样有个缺点,就是性能比较低,且交互效果不太好。
常见中文字符问题
如何只允许输入中文?
其实从过滤emoji的方法中可以想到,首先明确中文在Unicode的编码范围(中文编码范围)再加上中文符号的Unicode编码,基本上就可以得出中文的编码范围
只允许输入中文的正则表达式
function filterNoChinese(str = ‘’) {
const reg = /[^(\u2000-\u206f|\u3000-\u303f|\uff00-\uffff|\u4e00-\u9fcb|\u3400-\u4db5|\u2f00-\u2fd5|\u2e80-\u2ef3|\uf900-\ufad9|\ue815-\ue86f|\ue400-\ue5e8|\ue600-\ue6cf|\u31c0-\u31e3|\u2ff0-\u2ffb|\u3105-\u312f|\u31a0-\u31ba|\u3007)]+/gu
return str.replace(reg, ‘’)
}
本文介绍了字符集的概念,从ASCII到Unicode的发展历程,详细解析了各字符集的存储方式和特点,包括GB2312、GBK、BIG5、GB18030以及UTF-8等。同时,文章探讨了emoji的起源,展示了emoji如何被Unicode编码,并讨论了在不同平台上的显示规则。最后,文章提及了处理字符串中emoji的常见问题,提供了过滤emoji的正则表达式和解决方案。
7万+

被折叠的 条评论
为什么被折叠?



