Unicode?UTF-8?GBK?……聊聊字符集和字符编码格式

文章推薦指數: 80 %
投票人數:10人

Unicode 字符集简介 · UTF-8 —— 一种变长的Unicode 字符编码转换格式 · 扩展:聊聊主要针对汉字的字符集——GBK 和GB18030. HackerPie成长,折腾,保持单纯Unicode?UTF-8?GBK?……聊聊字符集和字符编码格式12/Dec/2021·3minuteread按照习惯起个调从Unicode和UTF-8说起Unicode字符集简介UTF-8——一种变长的Unicode字符编码转换格式扩展:聊聊主要针对汉字的字符集——GBK和GB18030UTF-8编码格式分析一些有趣的字符编码格式的例子操作系统中的默认字符集和转换格式MySQL数据库中的字符集处理编程语言对字符集的处理HTTP中用Accept-Charset协商字符集HTML标记语言中指定字符编码总结参考资料按照习惯起个调作为程序员,经常会在编程语言、操作系统、网络以及文本编辑等多个层面遇上字符集或者字符编码的问题,尽管一般都能快速通过搜索引擎找到解决方案,但是对于这种字符集以及其相关的字符编码格式的知识,倒是未曾系统梳理。

恰逢近期有了一些收获,趁热记录分享下。

从Unicode和UTF-8说起对于类Unix操作系统(比如MacOS以及Linux操作系统等)的用户来说,会更多地接触UTF-8编码格式,我也是其中一个。

而我过往总是容易跟另一个词——Unicode混淆,所以,当我们在讨论UTF-8和Unicode的时候,我们在讨论什么?Unicode字符集简介当我们说Unicode的时候,是在讨论一种字符集(characterset)。

Unicode翻译成中文叫“统一码”,是一种可以简单理解为收录了世界上所有语言的文字和符号的全球标准。

大家知道,英语的基本组成元素是26个英文字母加上各种标点符号;而汉语的文字则相对繁杂,大量汉字,每个文字都有各自的拼音,拼音里还要区分音调,这里提到的汉字、拼音、音调以及汉字自身的标点符号,跟英语的英文字母以及标点符号等,统统收录在了Unicode字符集中,而类似的,还有繁体中文、日文、韩文、俄罗斯语、越南语、泰语、蒙古语等等。

收录了这么多的字符,就会带来一个问题:怎么整理和编排记录这些内容呢?编号!类比在一些常见的场景中,当一个集体中包含很多的个体时,为了用一种统一且简单的方式区分,我们最容易想到的就是编号。

比如,给班里的同学安排座位号,给学生安排学号,给员工安排工号,等等。

但是,计算机是不能直接理解十进制这种人类易于理解的数字的,它只能理解二进制的数值,所以,在计算机里,我们可以用编码(使用特定的二进制序列来表示一个特定的值)的方式来给这些字符和符号进行一一映射。

目前Unicode实际应用版本UCS-2在计算机中使用了2个字节来编码一个字符,也就是16位的编码空间,在表示上,采用类如U+????的形式,其中每个“?”都是一个十六进制数。

注意,Unicode还有个4字节编码版本,亦即UCS-4,不在这里讨论。

以下是一些示例的Unicode字符及其对应编码:字符编码值说明牛U+725B汉字ùU+00F9拼音u的四声,U+002C英文逗号,U+FF0C中文逗号😁U+D83Demoji表情:笑脸⚔U+2694emoji表情:剑是不是挺有意思的?另外是否也注意到,同样是逗号,但是英文的逗号和中文的逗号,并不是同一个符号,哪怕看起来非常相似!相信很多初学编程的同学也都踩过在代码中输入了中文逗号导致代码编译出错的坑吧!UTF-8——一种变长的Unicode字符编码转换格式上面Unicode的编码方式已经理解了,但是那还只是表示层面的,字符在计算机世界里,需要被传输和存储等,这种情况下又该设计呢?最简单的方式当然是直接原样使用每个字符的两个字节即可(事实上,UTF-16即是这种思路),但是这种方式有两种问题:对于英语这类只需要一些非常简单的字符就足够的语言来说,单字节的ASCII字符集(一种主要包含英文字母、数字和标点符号以及其他不可见字符的字符集,总共128个字符)刚好就足以使用,如果使用两个字节,无疑是浪费了一半的存储空间;取决于具体的字节序,我们在存储和传输层面还得考虑字符编码的大端序或者小端序问题。

为了解决这些问题,UTF-8应运而生。

UTF全称UnicodeTransformationFormat,中文“Unicode转换格式”。

UTF-8是一种变长编码,其最大的特点是完全兼容ASCII字符编码(本质上得益于Unicode完全兼容ASCII字符集),对于所有在ASCII字符集中出现的字符,其在UTF-8中也是使用完全一样的单字节表示,且二进制码值完全一致。

比如对比以下的字符,在ASCII字符集下以及Unicode字符集中,和使用UTF-8表示的值:字符ASCII码值(十进制表示)Unicode码值UTF-8表示A65U+004101000001a97U+006101100001957U+003900111001,44U+002C00101100因此,对于需要存储或者传输包含有较多纯英文字符的文本,UTF-8的这种格式能够节省更多的存储空间,比如磁盘或者内存以及网络带宽等!对于UTF-8的完整格式,稍后会有单独的一章来具体分析。

扩展:聊聊主要针对汉字的字符集——GBK和GB18030上面聊到UTF-8优化了英文存储空间占用的问题,而且Unicode也是优先收录了各类西方语言的字符。

那有没有专门针对我们汉字的方案呢?有的,GBK!GBK,全称“汉字内码扩展规范”,全名为《汉字内码扩展规范(GBK)》1.0版。

GBK共收录21886个汉字和图形符号,其中汉字(包括部首和构件)21003个,图形符号883个。

所以GBK是一种主要收录汉字的字符集。

GBK于1995年12月15日发布,而2000年国家质量技术监督局推出了GB18030-2000标准,用以取代GBK,GB18030完全兼容GBK。

而GB18030在本质上也算得上是一种Unicode的转换格式(UTF),只不过其转换要比UTF-8复杂得多,在此就不展开了。

稍后的一些例子中还会提到GBK或者GB18030,这里仅作简单介绍,有个印象,大致知道是个啥即可。

UTF-8编码格式分析UTF-8是一种变长(长度范围为1-4个字节)的字符编码格式,所以一个字符对应的字节长度,需要结合每个字节开头的比特位来确认,具体的规则是:对于UTF-8编码中的任意字节B,如果B的第一位为0,则B独立的表示一个字符(ASCII码);如果B的第一位为1,第二位为0,则B为一个多字节字符中的一个字节(非ASCII字符);如果B的前两位为1,第三位为0,则B为两个字节表示的字符中的第一个字节;如果B的前三位为1,第四位为0,则B为三个字节表示的字符中的第一个字节;如果B的前四位为1,第五位为0,则B为四个字节表示的字符中的第一个字节。

所以,对于最长的4字节编码,其可表示的最大位数为21(首字节剩余3位,后续3个字节,每个字节有6位,3+3x6=21)。

上面的规则比较绕,为了方便理解,我们来列举下所有可能的比特序列:①单字节的情况,对应ASCII: 0??????? ②双字节的情况,第一个字节必须110开头,第二个字节开头必须是10,剩余11位用于编码: 110?????10?????? ③三字节的情况,第一个字节必须1110开头,第二、三个字节开头都必须是10,剩余16位用于编码: 1110????10??????10?????? ④四字节的情况,第一个字节必须11110开头,后续三个字节开头都必须是10,剩余21位用于编码: 11110???10??????10??????10?????? 一些示例的对应的Unicode字符及其对应的UTF-8编码:类型Block字符Unicode编码UTF-8编码(16进制)UTF-8编码(二进制表示)单字节基本拉丁字母aU+0061\x6101100001双字节拉丁文补充集£U+00A3\xC2\xA31100001010100011三字节日文平假名のU+306E\xE3\x81\xAE111000111000000110101110四字节越南语𦓡U+D859\xF0\xA6\x93\xA111110000101001101001001110100001而我们熟悉的常见的汉字使用的都是3字节的编码。

一些有趣的字符编码格式的例子操作系统中的默认字符集和转换格式在Windows10简体中文版系统中,通过在命令提示符程序中输入命令chcp,可以查看到系统活动代码页为936,对应的编码格式为GBK。

而在Linux服务器和我个人的Macbook电脑(操作系统macOSCatalina10.15.7)上,通过打印LC_CTYPE变量可以确认系统缺省使用UTF-8格式。

系统间的这种缺省字符编码格式的差异,往往会导致一个操作系统下编辑保存好的文件,到了另一个操作系统下就会出现乱码。

除了文件内容乱码,也会存在文件名乱码的情况等,原因都是类似的。

MySQL数据库中的字符集处理有过MySQL数据库使用经验的同学一定对字符集的选择使用会有一些经验心得:注意避免默认的latin1字符集如果需要支持emoji表情字符,还需要注意使用utf8mb4字符集这又是什么原因呢?历史原因!首先,latin1字符集是多字节字符编码技术之前的技术,在MySQL4.0及更早之前的版本中缺省使用,可能出于向下兼容的因素,这个缺省逻辑一直保留到5.7版本,到了当前最新版8.0中,已经改为缺省utf8mb4。

另外,MySQL字符集在MySQL中有两套UTF-8的实现,大家容易想到的同名“utf8”的方案,每个字符最多占据3个字节的空间,是一套非完整的实现,因此MySQL后来将其正式名字改为“utf8mb3”,用以指示这是一套有缺陷的实现,而保留下来的“utf8”只不过是个别名而已了;如果需要完整的实现,需要使用“utf8mb4”。

至于为什么会出现这种情况,根本原因还是历史。

MySQL在4.1版本开始支持UTF-8编码格式,当时对utf-8的实现并未形成统一标准,而MySQL在其实现中限制了编码空间最多为3个字节,而UTF-8正式形成业界的标准化文档在这个事情之后。

编程语言对字符集的处理来到编程语言层面,随着utf-8格式正式化以及发展,编程语言在字符集处理的方式上,在不同版本上也有一些有趣的历史。

Ruby2.0以前的版本(不包含2.0),如果需要指定源码的编码格式为utf-8,需要在代码文件的开头加上魔法注释:#encoding:utf-8在Python3.0以前的版本(不包含3.0),如果需要指定源码的编码格式为utf-8,需要在代码文件的开头加上魔法注释:#-*-coding:utf-8-*-在Golang中,由于这门语言本身比较新,没有这类在老版本代码中显式声明编码格式的需要HTTP中用Accept-Charset协商字符集Accept-Charset请求头用来告知(服务器)客户端可以处理的字符集类型。

借助内容协商机制,服务器可以从诸多备选项中选择一项进行应用,并使用Content-Type应答头通知客户端它的选择。

以下是一个实际的HTTP协议中协商charset的实际例子: HTML标记语言中指定字符编码通常在HTML里声明UTF-8字符编码,使用如下: 总结字符集和字符编码技术无处不在,通过诸多实际案例展示和原理分析,看到了其有趣且应用广泛的一面。

希望这篇文章,能够帮助你更加系统全面地了解掌握对它的认识和应用!最后附上一张图,看下一些前面提到的例子中,比如编程语言或者数据库管理系统对UTF-8的应用和相伴发展概况,来结束这篇文章:参考资料Wikipedia:UnicodeUnicode查询Wikipedia:ASCIIWikipedia:汉字内码扩展规范Wikipedia:GB18030Wikipedia:UTF-8UTF-8encoder/decoderUTF-8SamplerOSCHINA:在线进制转换StackOverflow:UnicodesampletextfilefortestingforUnicoderelatedproblems?云+社区:如何查看windows操作系统的默认编码?CSDN:查看windows与Mac的默认系统编码IBM:ChangingyourlocaleonLinuxandUNIXsystemsStackOverflow:WhydoesMySQLuselatin1_swedish_ciasthedefault?MySQL::MySQL5.7ReferenceMySQL::MySQL8.0ReferenceStackOverflow:SetUTF-8asdefaultforRuby1.9.3StackOverlfow:WorkingwithUTF-8encodinginPythonsourceWikipedia:MySQLWikipedia:PythonMDN:Accept-CharsetMDN:Characterencoding(字符编码)Medium:InMySQL,neveruse“utf8”.Use“utf8mb4”.MySQL5.5Releasenotes« Kafka核心设计思考——来自官方文档的总结 我的刻意练习——双拼输入 »



請為這篇文章評分?