茶叶网站设计,建设工程监理招标网站,尺寸在线做图网站,网站单页站群此篇文章仅为网上资料的汇总#xff0c;方便自己查询使用#xff0c;原文如下#xff1a;
参考文章1:一文读懂字符编码 参考文章2:菜鸟教程字符集 参考文章3:百度字符集 参考文章4:一个线上BUG彻底搞懂MySQL字符集#xff0c;工作也快搞丢了 参考文章5:深入理解MySQL字符集…此篇文章仅为网上资料的汇总方便自己查询使用原文如下
参考文章1:一文读懂字符编码 参考文章2:菜鸟教程字符集 参考文章3:百度字符集 参考文章4:一个线上BUG彻底搞懂MySQL字符集工作也快搞丢了 参考文章5:深入理解MySQL字符集 文章目录 1. 字符集、字符编码定义2. 字符编码详解2.1 为什么计算机需要编码2.2 二进制其实不存在2.3 计算机编码转换过程2.3.1 输入码2.3.2 机内码2.3.3 字形码 3 字符编码的历史3.1 电报编码3.2 编码纪元3.3 百花齐放3.4 天下一统 4. 字符编码模型4.1 传统编码模型4.2 现代编码模型 5. 常用字符集和字符编码5.1 ASCII字符集编码5.2 ISO-8859系列5.3 GBXXXX字符集编码5.3.1 GB23125.3.2 GBK5.3.3 GB18030 5.4 BIG5字符集编码5.5 Unicode字符集UTF编码5.5.1 UCS UNICODE5.5.2 UTF-325.5.3 UTF-165.5.4 UTF-8 6. Unicode字符集UTF编码详解6.1 UNICODE6.1.1 背景介绍6.1.2 Unicode介绍6.1.3 与UCS的关系 6.2 UTF-16(Java内部编码)6.3 UTF-86.3.1 简介规则6.3.2 程序算法6.3.3 容错性 7. Accept-Charset/Accept-Encoding/Accept-Language/Content-Type/Content-Encoding/Content-Language8. mysql字符集 1. 字符集、字符编码定义
计算机中储存的信息都是用二进制数表示的而我们在屏幕上看到的英文、汉字等字符是二进制数转换之后的结果。通俗的说按照何种规则将字符存储在计算机中如’a’用什么表示称为编码反之将存储在计算机中的二进制数解析显示出来称为解码如同密码学中的加密和解密。在解码过程中如果使用了错误的解码规则则导致’a’解析成’b’或者乱码。 字符集Charset是一个系统支持的所有抽象字符的集合。字符是各种文字和符号的总称包括各国家文字、标点符号、图形符号、数字等。 字符编码释义1Character Encoding是一套法则使用该法则能够对自然语言的字符的一个集合如字母表或音节表与其他东西的一个集合如号码或电脉冲进行配对。即在符号集合与数字系统之间建立对应关系它是信息处理的一项基本技术。通常人们用符号集合一般情况下就是文字来表达信息。而以计算机为基础的信息处理系统则是利用元件硬件不同状态的组合来存储和处理信息的。元件不同状态的组合能代表数字系统的数字因此字符编码就是将符号转换为计算机可以接受的数字系统的数称为数字代码。 字符编码释义2Character encoding也称字集码是把字符集中的字符编码为指定集合中的某一对象例如比特模式、自然数序列、8位组或者电脉冲以便文本在计算机中存储或者通信网络的传递。常见的例子是将拉丁字母表编码成摩斯电码和ASCII比如ASCII编码是将字母、数字和其它符号进行编号并用7比特的二进制来表示这个整数。 字符集Character set是多个字符的集合字符集种类较多每个字符集包含的字符个数不同常见字符集名称ASCII字符集、GB2312字符集、BIG5字符集、 GB18030字符集、Unicode字符集等。计算机要准确的处理各种字符集文字就需要进行字符编码以便计算机能够识别和存储各种文字。
2. 字符编码详解
2.1 为什么计算机需要编码
我们知道计算机的世界只有0和1如果没有字符编码我们看到的就是一串110010100101100111001…我们的沟通就好像是在对牛弹琴我看不懂它它看不懂我。字符编码就好比人类和机器之间的翻译程序把我们熟知的字符文字翻译成机器能读懂的二进制同时把二进制翻译成我们能看懂的字符。
编码Encode是信息从一种形式转换为另一种形式的过程比如用预先规定的方法将字符文字、数字、符号等、图像、声音或其它对象转换成规定的电脉冲信号或二进制数字。 我们现在看到的一幅幅图画听到的一首首音乐甚至我们写的一行行代码敲下的一个个字符所看到的所听到的都是那么的真实但其实在背后都是一串「01」的数字你昨天在手机上看到的那个心动女孩真实世界中并不存在只是计算机用「01」数字帮你生成的“骷髅”而已。
2.2 二进制其实不存在
你可能认为计算机中的数据就是「01」二进制但是实际上计算机中并没有二进制即便我们知道所有的内容都是存储在硬盘中但是你把它拆开可找不到里面有任何「0101」的数字里面也只有盘片、磁道。就算我们放大了去看盘片也只有凹凸不平的盘面凸起的地方是被磁化过的凹进去的地方是没有被磁化的只是我们给凸起的地方取了个名字叫数字「1」凹进的地方取名叫数字「0」。
同样内存里你也找不到二进制数字内存放大了看就是一堆电容组内存单元存储的是「0」还是「1」取决于电容是否有电荷有电荷我们认为他是「1」无电荷认为他是「0」。但是电容是会放电的时间一长代表「1」的电容会放电代表「0」的电容会吸电这也是我们内存不能断电的原因需要定期对电容进行充电保证「1」的电容电量有电。
再说显示器这个大家感受是最直接的你透过显示器看到的美女画皮、日月山川其实就是一个个不同颜色的发光二极管发出强弱不一的光点显示器就是一群发光二极管组成的矩阵其中每一个二极管可以被称为一个像素「1」表示亮「0」表示灭而我们平时能看到五彩的颜色是把三种颜色(红绿蓝三原色)的发光二极管做到了一起。那对于一个ASCII编码「65」最后又怎么显示成「A」的呢这就是显卡的功劳显卡中存储了每一个字符的图形数据也称字形码将二维矩阵的图形数据传给显示器成像。 因此所谓的0和1都是电流脉冲信号二进制其实是我们抽象出来的数学逻辑概念那我们为什么要用二进制表示 因为二进制只有两种状态使用有两个稳定状态的物理器件就可以表示二进制中的每一位例如用高低电平或电荷的正负性、灯的亮和灭都可以很方便地用「0」和「1」来表示这为计算机实现逻辑运算和逻辑判断提供了便利条件。
2.3 计算机编码转换过程
正因为计算机只能表示「01」的逻辑概念无法直接表示图片以及文字所以我们需要一定的转换过程。
这其实就是我们按照一定的规则维护了字符-数字的映射关系比如我们把「A」抽象成计算机中的「1」当我们看到1的时候就认为这是「A」本质上就是一张映射表理论上你可以随意给每个字符分配一个独一无二的编号character code字符编码比如下表
接下来我们来看下一个文字从输入-转码存储-输出显示/打印的简单流程首先我们知道计算机是美国人发明的规则是美国人定的键盘上的按键也都是英文字母所以编号不是你想怎么分配就怎么分配。对于英文字母的输入键盘和ASCII码之间是直接对应的键盘按键「A」对应的编号「65」存储到磁盘上也是「65」的二进制直译「01000001」这很好理解。
但是对于汉字输入就不是这么回事了键盘上可没有汉字对应的输入按键我们不可能直接敲出汉字字符。于是就有了输入码、机内码、字形码的转换关系输入码帮助我们把英文键盘按键转换成汉字字符机内码帮助我们把汉字字符转换成二进制序列字形码帮助我们把二进制序列输出到显示器成像。 2.3.1 输入码
我们模拟下汉字的输入过程首先打开txt文本敲下「nihao」的拼音字母然后输入栏会弹出多个符合条件的汉字词组最后我们会选择相应的编号就能实现汉字的输入。那这过程又是如何实现的呢
计算机领域有一句如同摩西十诫般的神圣哲言计算机科学领域的任何问题都可以通过增加一个间接的中间层来解决。
这里我们再加一层按键字母组合和汉字的映射表好比英汉字典这层我们称为输入码输入码到内码的过程就是一次查表转换操作比如「nihao」这几个ASCII字符大家可以随便修改映射表以及候选编号我可以把他映射成「心流时间」。
2.3.2 机内码
机内码也称内码是字符编码最核心的部分是字符集在计算机中实际存储、交换、通信使用的二进制编码通过内码我们可以达到高效率的存储、传输文本的目的。我们的外码输入码实现了键盘按键和字符的映射转换但是机内码是让字符真正变成了机器能读懂的二进制语言。
2.3.3 字形码
计算机中的字符都是以内码的二进制形式表示我们怎么把数字对应的字符在显示器上显示出来呢比如数字「1」代表汉字「你」怎么把「1」显示成「你」
这就需要依赖字形码字形码本质上是一个n*n 的像素点阵把某些位置的像素设置为白色用 1 表示其它位置像素设置为黑色用 0 表示每一个字符的字形都是预先存放在计算机内而这样的字形信息库我们称为字库。
比如中文「你」的点阵图这样一个 16*16 的像素矩阵需要 16 * 16 / 8 32 字节的空间来表示右边的字模信息称为字形码。不同的字库如宋体、黑体对同一个字符的字形编码是不同的。 所以字符编码到显示的字形码其实又是另一张查找表也就是字符编码-字形码的映射关系表。
其实我们也可以认为字符编码是字形码的一种压缩方式一个占32字节的像素点阵压缩成了2字节的机内码。
3 字符编码的历史
3.1 电报编码
从广义上来说编码的历史很悠久一直可以追溯到结绳记事的远古时期但跟现代字符编码比较接近的还是摩尔斯电码的发明自此开启了信息通信时代的大门。
摩尔斯电码是由美国人摩尔斯在1837年发明的比起ASCII还要早100多年在早期的无线电上作用非常大它是每个无线电通讯者需必知的它的是由点dot「.」和划dash「-」这两种符号所组成的电报中表达为短滴和长嗒跟二进制一样也是二元码。一个二元肯定不够表示我们的字母那么就用多个二元来表示比如嘀嗒「.-」代表字母「A」嗒嘀嘀嘀「-…」代表字母「B」。
摩尔斯电码表
3.2 编码纪元
计算机一开始发明出来时是用来解决数学计算问题的后来人们发现计算机还可以做更多的事例如文本处理等那个时候的机器都很大机器之间都是隔离的没考虑过机器的通信问题各大厂商也各干各个的搞自己的硬件搞自己的软件想怎么编码就怎么编码。 后来机器间需要相互通信的时候发现在不同计算机上显示出来的字符不一样在IBM上「00010100」数字代表「A」跑到微软系统上显示成了「B」大家就傻眼了。于是美国的标准化组织就跑出来制定了ASCII编码 (American Standard Code for Information Interchange)统一了游戏规则规定了常用符号用哪些二进制数来表示。
3.3 百花齐放 统一ASCII 码标准对于英语国家很开心但是ASCII编码只考虑了英文字母后来计算机传到欧洲地区法国人需要加个字母符号如é德国人又需要加几个字母Ä ä、Ö ö、Ü ü、ß幸好ASCII只用了前127个编号于是欧洲人就将ASCII没用完的编码128-255为自己特有的符号编码也能很好的一起玩耍。
但是等传到我们中国后做为博大精深的汉语言就彻底蒙圈了我们有几万个汉字255个编号完全不够用啊所以有了后来的多字节编码… 因此各个国家都推出了本国语言的编码表也就有了后来的 ISO 8859 系列、GB系列GB2312、GBK、GB18030、GB13000、Big5、EUC-KR、JIS … 不过为了能在计算机系统中通用这些扩展的编码均直接或间接兼容 ASCII 码。
而微软/IBM这些国际化产商为了把自己的产品卖到全世界就需要支持各个国家的语言要在不同的地方采用当地的编码方式于是他们就把全世界的编码方式都集中到一起并编上号并且起了个名字叫代码页Codepage又称内码表所以我们有时候也会看到xx代码页来指代某种字符编码比如在微软系统里 中文GBK编码对应的是936代码页繁体中文 Big5编码对应的是950代码页。
这些既兼容ASCII又互相之间不兼容的字符编码后来又统称为ANSI编码。看到下面这张图估计大家就很熟悉了window下面我们基本上都用ANSI编码保存。 ANSI的字面意思并非指字符编码而是美国的一个非营利组织是美国国家标准学会(American National Standards Institute)的缩写ANSI这个组织为字符编码做了很多标准制定工作后来大家习惯把这类混乱的多字节编码叫ANSI编码或者标准代码页。
ANSI编码只是一个范称一般代表系统默认的编码方式而且并不是确定的某一种编码方式——比如在Window操作系统里中国区ANSI编码指的是GB编码在香港地区ANSI编码指的是Big5编码在韩国ANSI编码指的是EUC-KR编码。
3.4 天下一统
由于各个国家各搞各的字符编码如果有些人想装逼中文里飚两句韩文怎么办呢不好意思你的逼级太高没法支持你选择了GB2312就只能打出中文字符。同时各大国际厂商在兼容各种字符编码问题上也深受折磨于是忍无可忍之下决定开发一套能容纳全世界所有字符的编码就有了后面大名鼎鼎的Unicode。
Unicode也叫万国码包括字符集、编码方案等Unicode是为了解决传统的字符编码方案的局限而产生的它为每种语言中的每个字符设定了统一并且唯一的二进制编码在这种语言环境下不会再有语言的编码冲突在同屏下也可以显示任何国家语言的内容这就是Unicode的最大好处。
在Unicode编码方案里常见的有四种编码实现方案UTF-7、 UTF-8、UTF-16、UTF-32最为知名的就是 UTF-8不过Unicode设计之初是采用双字节定长编码的UTF-16但是发现历史包袱太重推不动最后出了个变长的UTF-8才被广泛接受。
4. 字符编码模型
4.1 传统编码模型 在传统字符编码模型中基本上都是将字符集里的字符用十进制进行逐一的编号然后把十进制编号直接转成对应的二进制码可以说该字符编号就是字符的编码。
计算机在处理字符与数字的转换关系上其实就是查找映射表的过程。像ASCII编码就是给每个英文字符编一个独一无二的数字整个编码处理过程相对还是比较简单的计算机内部直接就映射成了二进制十进制的编号只是方便我们看的。
4.2 现代编码模型 Unicode编码模型采用了一个全新的编码思路将编码模型划分为4 个层次也有说5个层次的不过第五层是传输层的编码适配放在编码模型里严格来说不是很恰当。 第一层抽象字符集 ACRAbstract Character Repertoire定义抽象字符集合明确各个抽象字符 第二层编号字符集 CCSCoded Character Set将抽象字符集进行数字编号 第三层字符编码方式 CEFCharacter Encoding Form将字符编号编码为逻辑上的码元序列 第四层字符编码方案 CESCharacter Encoding Scheme将逻辑上的码元序列编码为物理字节序列 第一层抽象字符集 ACR 所谓抽象字符集就是抽象字符的合集是一个无序集合这里强调了字符是抽象的也就是不仅包括我们视觉上能看到的狭义字符比如「a」这样的有形字符也包括一些我们看不到的无形字符比如一些控制字符「DELETE」、「NULL」等。 抽象的另一层含义是有些字形是由多个字符组合成的比如西班牙语的 「ñ」 由「n」和「~」两个字符组成这一点上 Unicode 和传统编码标准不同传统编码标准多是将 ñ 视作一个独立的字符而 Unicode 中将其视为两个字符的组合。
同时一个字符也可能会有多种视觉上的字形表示比如一个汉字有楷、行、草、隶等多种形体这些都视为同一个抽象字符即字符集编码是对字符而非字形编码如何显示是字形库的事。 抽象字符集有开放与封闭之分。开放的字符集指还会不断新增字符的字符集封闭字符集是指不会新增字符的字符集。比如ASCII就是封闭式的只有128个字符以后也不会再加但是Unicode是开放式的会不断往里加新字符的已经从最初的 7163 个增加到现在的144,697 个字符。 第二层编号字符集 CCS 编号字符集就是对抽象字符集里的每个字符进行编号映射到一个非负整数的集合
编号一般用方便人类阅读的十进制、十六进制来表示比如「A」字符编号「65」「B」字符编号是「66」
大家需要清楚对于有些字符编码的编号就是存储的二进制序列如ASCII编码有些字符编码的编号跟存储的二进制序列并不一样比如GB2312、Unicode等。
另外编号字符集合是有范围限制的比如ASCII字符集范围是0127ISO-8859-1范围是0256而GB2312是用一个94*94的二维矩阵空间来表示Unicode是用Plane平面空间的概念来表示这称为字符集的编号空间。
编号空间中的一个位置称为码点 Code Point 代码点 。一个字符占用的码点所在的坐标非负整数值对或所代表的非负整数值就是该字符的码值码点编号。
ASCII码点编号 第三层字符编码方式 CEF 抽象字符集和编号字符集是站在方便我们理解的角度来看的所以最后我们需要翻译成计算机能懂的语言将十进制的编号转换成二进制的形式。
因此字符编码方式就是将字符集的码点编号转换成二进制码元序列 Code Unit Sequence 的过程。
码元字符编码的最小处理单元比如ASCII一个字符等于一个字节属于单字节码元UTF-16一个字符等于两个字节处理过程是按字「word」来处理所以是双字节码元UTF-8是多字节编码有单字节字符也有多字节字符每次处理是按单个单个字节解析处理所以处理最小单位是字节也属于单字节码元
这里大家可能会有疑问十进制直接转二进制不就好了吗为什么要单独抽出这么一层
早期的字符编码确实也是这么处理的十进制和二进制之间是直接转换过去的比如ASCII码字符「A」的十进制是「65」那对应的二进制就是「1000001」同时存储到硬盘里的也是这个二进制所以那时候的编码比较简单。
随着后来多字节字符编码Muilti-Bytes Character SetMBCS多字节字符集的出现字符编号和二进制之间不是直接转换过去的比如GB2312编码「万」字的区位编号是「4582」对应的二进制机内码却是「1100 1101 1111 0010」其十进制是「205242」。
如果这里不转换直接映射成二进制码会出什么问题呢「万」字的字符编号「4582」45在ASCII里是「-」82是「U」那到底是显示两个字符「-U」还是显示一个字符「万」字为了避免这种冲突 所以增加了前缀处理详细的过程会在下文具体来讲解。 第四层字符编码方案 CES 字符编码方案也称作“序列化格式“ Serialization Format 指的是将字符编号进行编码之后的码元序列映射为字节序列即字节流的形式以便经过编码后的字符能在计算机中进行处理、存储和传输。 字符编码方式CEF有点像我们数据库结构设计里的逻辑设计而这一层编码方案CES就像是物理设计了将码元序列映射为跟特定的计算机系统平台相关的物理意义上的二进制过程。 这里大家可能又会有疑问为什么二进制的码元序列和实际存储的二进制又会不一样呢这主要是计算机的大小端序造成的具体端序内容会在UTF-16编码部分详细介绍。 大小端序名词出自Jonathan Swift的《格列夫游记》一书 所有人都认为吃鸡蛋前原始的方法是打破鸡蛋较大的一端。可是当今皇帝的祖父小时候吃鸡蛋一次按古法打鸡蛋时碰巧将一个手指弄破了因此他的父亲当时的皇帝就下了一道敕令命令全体臣民吃鸡蛋时打破鸡蛋较小的一端违令者重罚。 老百姓们对这项命令极为反感。历史告诉我们由此曾发生过六次叛乱其中一个皇帝送了命另一个丢了王位…关于这一争端曾出版过几百本大部著作不过大端派的书一直是受禁的法律也规定该派的任何人不得做官。 5. 常用字符集和字符编码
常见字符集名称ASCII字符集、GB2312字符集、BIG5字符集、GB18030字符集、Unicode字符集等。计算机要准确的处理各种字符集文字需要进行字符编码以便计算机能够识别和存储各种文字。
5.1 ASCII字符集编码
很久以前计算机制造商都是按各自的方式来将字符渲染到屏幕上当时的计算机动不动可就是一套房子的大小这家伙可不是谁都能玩的起的那时人们并不关心计算机如何交流。随着上世纪七八十年代微处理器的出现计算机变得越来越小个人计算机开始进入大众的视线随后出现了井喷式的发展但是之前厂商都是各自为政没考虑过自家的产品要兼容别人家的东西导致在不同计算机体系间的数据转换变得十分蛋疼因此美国的标准协会在1967年制定出了ASCII编码到目前为止共定义了128个字符。
ASCIIAmerican Standard Code for Information Interchange美国信息交换标准代码是基于拉丁字母的一套电脑编码系统。它主要用于显示现代英语而其扩展版本EASCII则可以勉强显示其他西欧语言。它是现今最通用的单字节编码系统但是有被Unicode追上的迹象并等同于国际标准ISO/IEC 646。
ASCII字符集主要包括控制字符回车键、退格、换行键等可显示字符英文大小写字符、阿拉伯数字和西文符号。
ASCII编码将ASCII字符集转换为计算机可以接受的数字系统的数的规则。使用7位bits表示一个字符共128字符但是7位编码的字符集只能支持128个字符为了表示更多的欧洲常用字符对ASCII进行了扩展ASCII扩展字符集使用8位bits表示一个字符共256字符。ASCII字符集映射到数字编码规则如下图所示ASCII编码表 其中前 32 个031是不可见的控制字符32126 是可见字符127 是 DELETE 命令键盘上的 DEL 键。 其实早在ASCII之前IBM在1963年也推出过一套字符编码系统EBCDIC跟ASCII码一样囊括了控制字符、数字、常用标点、大小写英文字母。 EBCDIC 编码 但是他的字符编号并不是连续的这给后续程序处理带来了麻烦后来ASCII 编码吸取了 EBCDIC 的经验教训给英文单词分配了连续的编码方便程序处理因此被后来广泛接受。
ASCII 和 EBCDIC 编码相比除了字符连续排列之外最大的优点是ASCII 只用了一个字节的低 7 位最高位永远是 0。可别小看了这个最高位的 0看似无足轻重但这是ASCII设计最成功的地方后面介绍各编码原理的时候你会发现正是因为这个高位0其它编码规范才能对 ASCII 码无缝兼容使得 ASCII 被广泛接受。
ASCII的最大缺点是只能显示26个基本拉丁字母、阿拉伯数目字和英式标点符号因此只能用于显示现代美国英语而且在处理英语当中的外来词如naïve、café、élite等等时所有重音符号都不得不去掉即使这样做会违反拼写规则。而EASCII虽然解决了部份西欧语言的显示问题但对更多其他语言依然无能为力。因此现在的苹果电脑已经抛弃ASCII而转用Unicode。
5.2 ISO-8859系列
美国市场虽然统一了字符编码但是计算机制造商在进入欧洲市场的时候又遇到了麻烦欧洲的主流语言虽然也是用拉丁字母但却存在很多扩展体比如法语的「é」挪威语中的「Å」都无法用 ASCII 表示。但是大家发现ASCII后面的128个还没有被使用可以利用起来这对于欧洲主流语言就足够了。
于是就有了大家所熟知的这个ISO-8859-1(Latin-1),它只是扩展了ASCII后128个字符还是属于单字节编码同时为了兼容原先的 ASCII码当最高位是0的时候仍然表示原先的 ASCII 字符不变当最高位是1的时候表示扩展的欧洲字符。 但是到这里还没有完刚说了这只是欧洲主流的语言但主流语言里没有法语使用的 œ、Œ、Ÿ 三个字母也没有芬兰语使用的 Š、š、Ž、ž 而单字节编码里的256个码点都被用完了于是就出现了更多的变种 ISO-8859-2/3/…/16 系列他们都兼容 ASCII但彼此间又不完全兼容。 ISO-8859-n系列字符集如下 ISO8859-1 字符集也就是 Latin-1是西欧常用字符包括德法两国的字母。 ISO8859-2 字符集也称为 Latin-2收集了东欧字符。 ISO8859-3 字符集也称为 Latin-3收集了南欧字符。 ISO8859-4 字符集也称为 Latin-4收集了北欧字符。 ISO8859-5 字符集也称为 Cyrillic收集了斯拉夫语系字符。 ISO8859-6 字符集也称为 Arabic收集了阿拉伯语系字符。 ISO8859-7 字符集也称为 Greek收集了希腊字符。 … 5.3 GBXXXX字符集编码
计算机发明之处及后面很长一段时间只用应用于美国及西方一些发达国家ASCII能够很好满足用户的需求。但是当天朝也有了计算机之后为了显示中文必须设计一套编码规则用于将汉字转换为计算机可以接受的数字系统的数。
当计算机进入东亚国家的时候厂商们更傻眼了美国和欧洲国家语言基本都是表音字符一个字节就足够用了但亚洲国家有不少是表意字符字符个数动辄几万十几万的一个字节完全不够用所以我们国家有关部门按照ISO规范设计了GB2312双字节编码但是GB2312是一个封闭字符集只收录了常用字符总共也就7000多个字符因此为了扩充更多的字符包括一些生僻字才有了之后的GBK、GB18030、GB13000“GB” 为 “国标” 的汉语拼音首字母缩写。
按照 GB 系列编码方案在一段文本中如果一个字节是 0127那么这个字节的含义与 ASCII 编码相同否则这个字节和下一个字节共同组成汉字或是 GB 编码定义的其他字符所以GB系列都是兼容ASCII编码的。
5.3.1 GB2312
天朝专家把那些127号之后的奇异符号们即EASCII取消掉规定一个小于127的字符的意义与原来相同但两个大于127的字符连在一起时就表示一个汉字前面的一个字节他称之为高字节从0xA1用到 0xF7后面一个字节低字节从0xA1到0xFE这样我们就可以组合出大约7000多个简体汉字了。在这些编码里还把数学符号、罗马希腊的 字母、日文的假名们都编进去了连在ASCII里本来就有的数字、标点、字母都统统重新编了两个字节长的编码这就是常说的全角字符而原来在127号以下的那些就叫半角字符了。
上述编码规则就是GB2312。GB2312或GB2312-80是中国国家标准简体中文字符集全称《信息交换用汉字编码字符集·基本集》又称GB0由中国国家标准总局发布1981年5月1日实施。GB2312编码通行于中国大陆新加坡等地也采用此编码。中国大陆几乎所有的中文系统和国际化的软件都支持GB2312。GB2312的出现基本满足了汉字的计算机处理需要它所收录的汉字已经覆盖中国大陆99.75%的使用频率。对于人名、古汉语等方面出现的罕用字GB2312不能处理这导致了后来GBK及GB 18030汉字字符集的出现。下图是GB2312编码的开始部分由于其非常庞大只列举开始部分具体可查看GB2312简体中文编码表 GB2312是使用两个字节来表示汉字的编码标准共收入汉字6763个和非汉字图形字符682个为了避免与 ASCII 字符编码0~127相冲突规定表示一个汉字的编码字节其值必须大于127即字节的最高位为 1 并且必须是两个大于 127 的字节连在一起来共同表示一个汉字 GB2312 为双字节编码所以GB2312 属于变长编码当是英文字符的时候占一个字节中文字符的时候占两个字节可以认为 GB2312是对 ASCII 的中文扩展。 GB2312字符集编号空间是一个94*94的二维表行表示区高位字节列表示位低位字节每区有94个位每个区位对应一个字符称为区位码。区位码上加2020H就得到国标码国标码上加8080H就得到常用的计算机机内码。这里引入了区位码、国标码、机内码概念下面我们说下三者的关系 国标码 国标码是我国汉字信息交换的标准编码规定由4位16进制数组成用两个低7位字节表示为了避开 ASCII 字符中的前32个控制指令字符所以每个字节都是从第33个编号开始如下图所示
区位码 由于上述国标码的16进制可编码区不够直观不方便我们使用所以我们把他映射成了十进制的94*94二维表编号空间我们称之为区位码同时区位码也可以当成一种外码使用输入法可以直接切换成区位码进行汉字输入不过这种输入法无规则可言 人们很难记住区位编号用的人也不多了。
下图是区位码的二维表比如「万」字是45 区 82 位所以「万」 字的区位码是「4582」。
其中
01~09区(682个)特殊符号、数字、英文字符、制表符等包括拉丁字母、希腊字母、日文平假名及片假名字母、俄语西里尔字母等在内- 的682个全角字符10~15区空区留待扩展16~55区(3755个)常用汉字(也称一级汉字)按拼音排序56~87区(3008个)非常用汉字(也称二级汉字)按部首/笔画排序88~94区空区留待扩展。
机内码 GB2312国标码规范是覆盖掉ASCII中可见部分的符号和英文字母使用两个7位码将其中的英文字母和符号重新编入但是这样产生一个弊端早期用ASCII码编码的英文文章无法打开一打开就是乱码也就是说应该要兼容早期ASCII码而不是覆盖它后来微软为了解决这个问题将字节的最高位设为1因为ASCII中使用7位最高位为0转换后的编码称为机内码(内码)这种方式本质上是修改了GB2312的编码标准最后被大家接受沿用。
总结下三者转换关系区位码 — 区码和位码分别 32即 20H 得到国标码 — 再分别 128即 80H得到机内码与 ACSII 码不再冲突
5.3.2 GBK
GBK即“国标扩展”的意思因为GB2312双字节的最高位都要求大于1上限也不会超过1万个字符所以对此进行了扩展对GB2312的字符不重新编码直接沿用因此完全兼容GB2312。GBK虽然也是双字节编码但是只要求第一个字节大于 127 就固定表示这是一个汉字的开始正因为如此GBK的编码空间比GB2312大很多。
GBK 整体编码范围为 8140-FEFE首字节在 81-FE 之间尾字节在 40-FE 之间剔除 xx7F 一条线总计 23940 个码位共收入 21886 个汉字和图形符号其中 GBK/1 收录除 GB 2312 字符外的其他增补字符GBK/2 收录 GB2312 字符GBK/3 收录 CJK 字符GBK/4 收录 CJK 字符和增补字符GBK/5 为非中文字符UDC 为用户自定义字符。 详细如下如所示 → 这里大家可能会有两个疑问为什么尾字节要从40开始而不是00开始为什么要排除 FF、xx7F这两条线的编号
GBK的尾字节编码高位没有强制要求是1当高位是0时跟ASCII码是冲突的ASCII码里00-40之间大部分都是控制字符所以排除控制字符主要是为了防止丢失高字节导致出现系统性严重后果
排除FF是为了兼容GB2312GB2312这个位是保留不使用的而7F表示DEL字符就是向后删除一个字符如果传输过程中丢失首字节那么就会出现严重的后果所以需要将xx7F也排除这是所有编码方案都需要注意的地方。
5.3.3 GB18030
随着计算机的发展GBK的2万多个字符也还是扛不住于是2000年我国又制定了新标准 GB18030用来替代 GBK 标准。GB18030是强制性标准现在在中国大陆销售的软件都支持 GB18030。
GB18030其实是对齐Unicode标准的里面包括了所有Unicode字符集也算是Unicode的一种实现(UTF)。
那既然有了UTF我们为什么还要搞一套Unicode实现
主要是UTF-8/UCS-2他们是不兼容GB2312的如果直接升级那么就全乱码了所以GB18030是为了兼容GB系列是GBK、GB2312的超集当我们原先的GB2312(GBK)软件考虑升级到国际化Unicode时可以直接使用GB18030进行升级。
GB18030虽然也是GB2312的扩展但它和GBK的扩展方式不一样GBK主要是充分利用了GB2312的一些没定义的编码空间而GB18030采用的是字节变长编码单字节区兼容ASCII、双字节区兼容GBK、四字节区对齐所有Unicode 码位。
实现原理上主要是采用第二字节未使用到的0x30~0x39编码空间来判断是否四字节。
单字节其值从0到0x7F。双字节第一个字节的值从0x81到0xFE第二个字节的值从0x40到0xFE不包括0x7F。四字节第一个字节的值从0x81到0xFE第二个字节的值从0x30到0x39第三个字节的值从0x81到0xFE第四个字节的值从0x30到0x39。
由于GB 2312-80只收录6763个汉字有不少汉字如部分在GB 2312-80推出以后才简化的汉字如啰部分人名用字如中国前总理朱镕基的镕字台湾及香港使用的繁体字日语及朝鲜语汉字等并未有收录在内。于是厂商微软利用GB 2312-80未使用的编码空间收录GB 13000.1-93全部字符制定了GBK编码。根据微软资料GBK是对GB2312-80的扩展也就是CP936字码表 (Code Page 936)的扩展之前CP936和GB 2312-80一模一样最早实现于Windows 95简体中文版。虽然GBK收录GB 13000.1-93的全部字符但编码方式并不相同。GBK自身并非国家标准只是曾由国家技术监督局标准化司、电子工业部科技与质量监督司公布为技术规范指导性文件。原始GB13000一直未被业界采用后续国家标准GB18030技术上兼容GBK而非GB13000。
GB 18030全称国家标准GB 18030-2005《信息技术 中文编码字符集》是中华人民共和国现时最新的内码字集是GB 18030-2000《信息技术 信息交换用汉字编码字符集 基本集的扩充》的修订版。与GB 2312-1980完全兼容与GBK基本兼容支持GB 13000及Unicode的全部统一汉字共收录汉字70244个。GB 18030主要有以下特点
与UTF-8相同采用多字节编码每个字可以由1个、2个或4个字节组成。编码空间庞大最多可定义161万个字符。支持中国国内少数民族的文字不需要动用造字区。汉字收录范围包含繁体汉字以及日韩汉字。
GB18030编码总体结构图 本规格的初版使中华人民共和国信息产业部电子工业标准化研究所起草由国家质量技术监督局于2000年3月17日发布。现行版本为国家质量监督检验总局和中国国家标准化管理委员会于2005年11月8日发布2006年5月1日实施。此规格为在中国境内所有软件产品支持的强制规格。
5.4 BIG5字符集编码
Big5又称为大五码或五大码是使用繁体中文正体中文社区中最常用的电脑汉字字符集标准共收录13,060个汉字。中文码分为内码及交换码两类Big5属中文内码知名的中文交换码有CCCII、CNS11643。Big5虽普及于台湾、香港与澳门等繁体中文通行区但长期以来并非当地的国家标准而只是业界标准。倚天中文系统、Windows等主要系统的字符集都是以Big5为基准但厂商又各自增加不同的造字与造字区派生成多种不同版本。2003年Big5被收录到CNS11643中文标准交换码的附录当中取得了较正式的地位。这个最新版本被称为Big5-2003。
Big5码是一套双字节字符集使用了双八码存储方法以两个字节来安放一个字。第一个字节称为高位字节第二个字节称为低位字节。高位字节使用了0x81-0xFE低位字节使用了0x40-0x7E及0xA1-0xFE。在Big5的分区中
5.5 Unicode字符集UTF编码
伟大的创想Unicode——不得不单独说Unicode
像天朝一样当计算机传到世界各个国家时为了适合当地语言和字符设计和实现类似GB232/GBK/GB18030/BIG5的编码方案。这样各搞一套在本地使用没有问题一旦出现在网络中由于不兼容互相访问就出现了乱码现象。
为了解决这个问题一个伟大的创想产生了——Unicode。Unicode编码系统为表达任意语言的任意字符而设计。它使用4字节的数字来表达每个字母、符号或者表意文字(ideograph)。每个数字代表唯一的至少在某种语言中使用的符号。并不是所有的数字都用上了但是总数已经超过了65535所以2个字节的数字是不够用的。被几种语言共用的字符通常使用相同的数字来编码除非存在一个在理的语源学(etymological)理由使不这样做。不考虑这种情况的话每个字符对应一个数字每个数字对应一个字符。即不存在二义性。不再需要记录模式了。U0041总是代表’A’即使这种语言没有’A’这个字符。
在计算机科学领域中Unicode统一码、万国码、单一码、标准万国码是业界的一种标准它可以使电脑得以体现世界上数十种文字的系统。Unicode 是基于通用字符集Universal Character Set的标准来发展并且同时也以书本的形式[1]对外发表。Unicode 还不断在扩增 每个新版本插入更多新的字符。直至目前为止的第六版Unicode 就已经包含了超过十万个字符在2005年Unicode 的第十万个字符被采纳且认可成为标准之一、一组可用以作为视觉参考的代码图表、一套编码方法与一组标准字符编码、一套包含了上标字、下标字等字符特性的枚举等。Unicode 组织The Unicode Consortium是由一个非营利性的机构所运作并主导 Unicode 的后续发展其目标在于将既有的字符编码方案以Unicode 编码方案来加以取代特别是既有的方案在多语环境下皆仅有有限的空间以及不兼容的问题。
可以这样理解Unicode是字符集UTF-32/ UTF-16/ UTF-8是三种字符编码方案。所以说一个字节占多少字节不是由字符集决定的而是由字符编码决定的
5.5.1 UCS UNICODE
通用字符集Universal Character SetUCS是由ISO制定的ISO 10646或称ISO/IEC 10646标准所定义的标准字符集。历史上存在两个独立的尝试创立单一字符集的组织即国际标准化组织ISO和多语言软件制造商组成的统一码联盟。前者开发的 ISO/IEC 10646 项目后者开发的统一码项目。因此最初制定了不同的标准。
1991年前后两个项目的参与者都认识到世界不需要两个不兼容的字符集。于是它们开始合并双方的工作成果并为创立一个单一编码表而协同工作。从Unicode 2.0开始Unicode采用了与ISO 10646-1相同的字库和字码ISO也承诺ISO 10646将不会替超出U10FFFF的UCS-4编码赋值以使得两者保持一致。两个项目仍都存在并独立地公布各自的标准。但统一码联盟和ISO/IEC JTC1/SC2都同意保持两者标准的码表兼容并紧密地共同调整任何未来的扩展。在发布的时候Unicode一般都会采用有关字码最常见的字型但ISO 10646一般都尽可能采用Century字型。
5.5.2 UTF-32
上述使用4字节的数字来表达每个字母、符号或者表意文字(ideograph)每个数字代表唯一的至少在某种语言中使用的符号的编码方案称为UTF-32。UTF-32又称UCS-4是一种将Unicode字符编码的协定对每个字符都使用4字节。就空间而言是非常没有效率的。
这种方法有其优点最重要的一点就是可以在常数时间内定位字符串里的第N个字符因为第N个字符从第4×Nth个字节开始。虽然每一个码位使用固定长定的字节看似方便它并不如其它Unicode编码使用得广泛。
5.5.3 UTF-16
尽管有Unicode字符非常多但是实际上大多数人不会用到超过前65535个以外的字符。因此就有了另外一种Unicode编码方式叫做UTF-16(因为16位 2字节)。UTF-16将0–65535范围内的字符编码成2个字节如果真的需要表达那些很少使用的星芒层(astral plane)内超过这65535范围的Unicode字符则需要使用一些诡异的技巧来实现。UTF-16编码最明显的优点是它在空间效率上比UTF-32高两倍因为每个字符只需要2个字节来存储除去65535范围以外的而不是UTF-32中的4个字节。并且如果我们假设某个字符串不包含任何星芒层中的字符那么我们依然可以在常数时间内找到其中的第N个字符直到它不成立为止这总是一个不错的推断。其编码方法是
如果字符编码U小于0x10000也就是十进制的0到65535之内则直接使用两字节表示如果字符编码U大于0x10000由于UNICODE编码范围最大为0x10FFFF从0x10000到0x10FFFF之间 共有0xFFFFF个编码也就是需要20个bit就可以标示这些编码。用U’表示从0-0xFFFFF之间的值将其前 10 bit作为高位和16 bit的数值0xD800进行 逻辑or 操作将后10 bit作为低位和0xDC00做 逻辑or 操作这样组成的 4个byte就构成了U的编码。
对于UTF-32和UTF-16编码方式还有一些其他不明显的缺点。不同的计算机系统会以不同的顺序保存字节。这意味着字符U4E2D在UTF-16编码方式下可能被保存为4E 2D或者2D 4E这取决于该系统使用的是大尾端(big-endian)还是小尾端(little-endian)。只要文档没有离开你的计算机它还是安全的——同一台电脑上的不同程序使用相同的字节顺序(byte order)。但是当我们需要在系统之间传输这个文档的时候也许在万维网中我们就需要一种方法来指示当前我们的字节是怎样存储的。不然的话接收文档的计算机就无法知道这两个字节4E 2D表达的到底是U4E2D还是U2D4E。
为了解决这个问题多字节的Unicode编码方式定义了一个字节顺序标记(Byte Order Mark)它是一个特殊的非打印字符你可以把它包含在文档的开头来指示你所使用的字节顺序。对于UTF-16字节顺序标记是UFEFF。如果收到一个以字节FF FE开头的UTF-16编码的文档你就能确定它的字节顺序是单向的(one way)的了如果它以FE FF开头则可以确定字节顺序反向了。
5.5.4 UTF-8
UTF-88-bit Unicode Transformation Format是一种针对Unicode的可变长度字符编码定长码也是一种前缀码。它可以用来表示Unicode标准中的任何字符且其编码中的第一个字节仍与ASCII兼容这使得原来处理ASCII字符的软件无须或只须做少部份修改即可继续使用。因此它逐渐成为电子邮件、网页及其他存储或传送文字的应用中优先采用的编码。互联网工程工作小组IETF要求所有互联网协议都必须支持UTF-8编码。
UTF-8使用一至四个字节为每个字符编码
128个US-ASCII字符只需一个字节编码Unicode范围由U0000至U007F。带有附加符号的拉丁文、希腊文、西里尔字母、亚美尼亚语、希伯来文、阿拉伯文、叙利亚文及它拿字母则需要二个字节编码Unicode- 范围由U0080至U07FF。其他基本多文种平面BMP中的字符这包含了大部分常用字使用三个字节编码。其他极少使用的Unicode辅助平面的字符使用四字节编码。
在处理经常会用到的ASCII字符方面非常有效。在处理扩展的拉丁字符集方面也不比UTF-16差。对于中文字符来说比UTF-32要好。同时在这一条上你得相信我因为我不打算给你展示它的数学原理。由位操作的天性使然使用UTF-8不再存在字节顺序的问题了。一份以utf-8编码的文档在不同的计算机之间是一样的比特流。
总体来说在Unicode字符串中不可能由码点数量决定显示它所需要的长度或者显示字符串之后在文本缓冲区中光标应该放置的位置组合字符、变宽字体、不可打印字符和从右至左的文字都是其归因。所以尽管在UTF-8字符串中字符数量与码点数量的关系比UTF-32更为复杂在实际中很少会遇到有不同的情形。
优点
UTF-8是ASCII的一个超集。因为一个纯ASCII字符串也是一个合法的UTF-8字符串所以现存的ASCII文本不需要转换。为传统的扩展ASCII字符集设计的软件通常可以不经修改或很少修改就能与UTF-8一起使用。使用标准的面向字节的排序例程对UTF-8排序将产生与基于Unicode代码点排序相同的结果。尽管这只有有限的有用性因为在任何特定语言或文化下都不太可能有仍可接受的文字排列顺序。UTF-8和UTF-16都是可扩展标记语言文档的标准编码。所有其它编码都必须通过显式或文本声明来指定。任何面向字节的字符串搜索算法都可以用于UTF-8的数据只要输入仅由完整的UTF-8字符组成。但是对于包含字符记数的正则表达式或其它结构必须小心。UTF-8字符串可以由一个简单的算法可靠地识别出来。就是一个字符串在任何其它编码中表现为合法的UTF-8的可能性很低并随字符串长度增长而减小。举例说字符值C0,C1,F5至FF从来没有出现。为了更好的可靠性可以使用正则表达式来统计非法过长和替代值可以查看W3 FAQ: Multilingual Forms上的验证UTF-8字符串的正则表达式。
缺点
因为每个字符使用不同数量的字节编码所以寻找串中第N个字符是一个O(N)复杂度的操作 — 即串越长则需要更多的时间来定位特定的字符。同时还需要位变换来把字符编码成字节把字节解码成字符。
6. Unicode字符集UTF编码详解
6.1 UNICODE
6.1.1 背景介绍
在统一码之前各国创造了大量的节编码标准有单字节的、双字节的如 GB 2312、Shift JIS、Big5 、ISO8859等各自又相互不兼容。在1987 年苹果、Sun、微软等公司开始讨论囊括全世界所有字符的统一编码标准组成了 Unicode 联盟这个期间做了很多研讨工作讨论核心要点如下
目前世界上有多少个字符需要几个字节存储 工作组统计了当时全世界的报纸等刊物结论是两个字节足以囊括全世界有实用意义的字符当然这只统计了当前使用的字符不包括古代语言或者废弃语言。采用固定长度编码还是变长编码 一种采用变长编码形式对于 ASCII 字符使用一个字节其他字符使用两个字节类似 GBK 另一种采用定长编码形式不管是不是 ASCII 字符统一使用两个字节。
方案选择上主要从计算机处理过程中的时间和空间两个维度也就是编解码的执行效率和存储大小两方面最后结论是采用双字节定长编码因为定长带来的空间变大在整体传输、存储成本上其实影响并不大而定长编码处理效率会明显高于变长编码所以早期 Unicode 采用了定长编码形式。
中、日、韩中有很多相近的表意文字是否可以统一
由于汉字表意文字字符量较大如果可以统一那么能大幅减少收录汉字的数量
所以最初收录汉字遵循两个基本原则表意文字认同原则和字源分离原则。
所谓表意文字认同原则即“只对字不对形”编码将同一字的不同字形即异体字合并。例如“房”字的第一笔在中日韩的写法都不同但它本身是同一个字只给一个编码而写法的不同交由字体进行区分。
字源分离原则是指一个字源中同时收录了同一个字的不同字形则给予两个字形分别编码。例如之前GBK中就收录了“戶”、“户”、“戸”三个字那么Unicode也需要保留三个字如果直接合并会造成使用上的困扰。 例如下面这句话如果不做字源分离会是什么情况呢 原句 户有三种写法分别是“戶”、“户”、“戸” 改写后户有三种写法分别是“户”、“户”、“户” 6.1.2 Unicode介绍
Unicode 称为统一码也叫万国码是按现代编码模型进行设计的一套字符编码体系涵盖抽象字符集、编号、逻辑编码、编码实现。Unicode是为了解决传统的字符编码方案的局限而产生的在这种语言环境下不会再有语言的编码冲突可以在同屏下显示任何国家的语言。
UTF-n编码Unicode Transformation Format Unicode字符集转换格式n表示码元位数是Unicode这套编码体系里的编码实现CES部分像UTF-8、UTF-16、UTF-32都是将数字转换到实际的二进制编码实现Unicode的编码实现除了UTF系列之外还有UCS-2/4GB18030等。但是现在很多人误把Unicode当成只是一个字符编号这其实是不对的。
Unicode可以容纳世界上所有国家的文字和符号其编号范围是0-0x10FFFF有1,114,112个码位为了方便管理划分成17个平面现已定义的码位有238,605个分布在平面0、平面1、平面2、平面14、平面15、平面16。其中平面0又称为基本多语言平面Basic Multilingual Plane简称BMP这个平面基本涵盖了当今世界上正在使用中的常用字符。我们平常用到的字符一般都是位于 BMP 平面上的其范围拥有 65,536 个码点其他平面统称增补平面关于平面的概念会在UTF-16章节详细介绍。
6.1.3 与UCS的关系 说起Unicode我们不得不提UCS全称Universal Multiple-Octet Coded Character Set 通用多八位编码字符集国际标准编号ISO/IEC 10646是由 ISO 和 IEC 两家国际标准组织联合成立的工作组设计的一套新的统一字符集项目目的与Unicode 联盟一样致力于开发一款全世界通用的编码集。
早在1984 年ISO 和 IEC 两家组织就成立了一个联合工作组来设计一套新的统一字符集标准但是这两个组织都不知道对方的存在直到Unicode联盟1988年发布了Unicode草案UCS草案1989年发布才发现大家在做同一件事没有必要搞两套标准 所以后面又考虑合并
由于UCS 最初设计的是 31 位编码空间(UCS-4编码实现)可以容纳 2^31 约 21 亿个字符而Unicode是16位空间UTF-16编码实现所以最开始Unicode 打算作为 UCS 的真子集即 Unicode 中的每个字符都存在于 UCS 中而且两者的码点相同但 UCS 中的字符编号超过65,536的则不一定存在于 Unicode 中。
不过由于双方利益关系并没有说谁解散谁最后双方作出一些妥协保持一致共同发展两个标准中相同字符的编码码点必须是一样的这是一个屁股决定脑袋的决策如果最初Unicode知道UCS的存在就不会再出现Unicode了。当然合并工作不是一蹴而就的而是经过多轮迭代 ISO/IEC 和 Unicode在 1993 年发布了第一版相互兼容版本到了 1996年Unicode 2.0标准发布时Unicode 字符集和 UCS 字符集即 ISO/IEC 10646-1 基本保持了一致同时Unicode为了跟UCS的四字节保持一致推出了UTF-32编码实现UCS为了跟Unicode的两字节保持一致推出了UCS-2编码实现。
所以现在我们可以认为UCS和Unicode是同一个东西比如我们常见的java内部运行就采用的是UTF-16编码而window操作系统采用的是UCS-2他们都是同一个Unicode标准。
→ 为什么这里使用的是2字节编码而不是4字节呢先留个悬念后续会详细讲解
6.2 UTF-16(Java内部编码)
UTF是Unicode Transfer Format的缩写即把Unicode转做某种格式的意思所以UTF-16是Unicode编码里的其中一种实现方式16代表的是字节位数占两个字节UTF-32则表示4个字节。
Unicode 设计之初是采用UTF-16这种双字节定长编码的其字符编号就是对应的二进制编号也就是说第二层的CCS和第三层的CEF是一致的。比如汉字「万」的 Unicode 码点是 「U4E07」其二进制序列就是直译的「0100 1110 0000 0111 」这种编码方式的优点是高效不需要检查标志位但缺点是不兼容ASCIIASCII编码的文本都会显示乱码。
不过后来Unicode联盟发现 16 位编码空间根本不够用与此同时 ISO/IEC组织也觉得 UCS的 32 位编码空间太多了实际中根本没有几十亿字符也挺浪费空间的所以最终 Unicode 联盟和 ISO/IEC 工作组达成一致两者使用统一的编码空间「 0000 ~ 10FFFF」即 UCS 保证永远不分配大于 10FFFF 的字符码点而且双方在字符编码上保持同步即一方标准中增加了字符也要通知另一方同步。
于是Unicode在UTF-16基础上拓展编码空间到 21 位UCS则搞了一个双字节的UCS-2编码实现。
→ UTF-16 编码是双字节的上限也只有6w多个码点怎么让他支持到10FFFF(100w)个码点呢
本质就是多加几个字节来表示更多的字符只是UTF-16不像UCS那样采用定长4字节而是使用变长的形式但是这个跟UTF-8变长方式又不太一样他是采用代理对的方式实现大部分常用字符用一个码元表示(定长2个字节)其他扩展的特殊字符用两个码元表示(定长4字节)。 代理对 UTF-16跟UTF-8、GB系列等都算是变长字节但是设计初衷却不一样像GBK是为了兼容ASCII但是UTF-16一开始就没考虑要兼容ASCII所以他的变长是为了节约存储空间而采用的自然增长方案当空间不够的时候增长到4个字节。
那问题来了我怎么知道存储的4个字节是表示一个字符还是两个字符呢比如当程序遇到字节序列01001110 00101101 01010110 11111101时到底是判断成一个字符还是两个字符
这就需要一个前导识别比如GB2312识别第一个字节高位是不是1来判断是单字节还是双字节但是UTF-16的高位1已经被用来编码了当然这也难不倒我们第一位被用了那么就用前几位的组合形式。
UTF-16采用了代理对来解决也就是高半区编码前两个字节范围D800-DBFF称为代理码点低半区编码后两个字节范围DC00-DFFF组成一个四个字节表示的字符。 上述前导6位组合也是有讲究的ISO组织要求编号范围是0~10FFFF()也就是说用20位就可以表示10FFFF个字符对于双码元就是每个码元各自负责10位一个码元是16位数字位占去10位后剩下的6位做为前导位。
当UTF-16使用一个码元表示的时候Unicode字符编号跟码元序列是等值映射的但是当采用双码元后字符编号跟码元序列就需要转换了下面是码元和Unicode编号值之间的计算公式 平面空间 UTF-16把编码空间0000 ~ 10FFFF切成了17个平面其实就是划分成17个区块每个平面空间码点数都是65536个第一个平面称为基本多语言平面Basic Multilingual Plane简称BMP这个平面涵盖了当今世界上最常用的字符固定使用定长两个字节除此之外的字符都放到增补平面里都是使用两个码元的定长4个字节下面是各个平面的用途 增补平面的编号是采用双码元4个字节来表示的去除代理对之后有效位数是20位然后将这20位的编号再划成16个平面区域其中高半区的数字位里取出4位表示平面剩下的16位表示每个平面可以表示的字符数也就是2的16次方65536个两个字节大小 UTF-16可看成是UCS-2的父集。在没有辅助平面前UTF-16与UCS-2所指的是同一的意思。但当引入辅助平面字符后就称为UTF-16了。 字节序 字节序顾名思义是指字节的顺序对于单字节编码来说一个字符对应一个字节也就不存在字节序问题但是对于UTF-16这种定长多字节编码就有字节顺序问题了。字节序其实跟操作系统和底层硬件有关不仅只是UTF-16这种多字节编码存在字节序只要是多字节类型的数据都存在字节顺序问题比如short、int、long。
为了方便说明我们这里举个例子比如存一个整数值「305419896」对应16进制是0x12345678有人习惯从左到右按顺序去存也有人说高位当然要放到高位地址而低位放到低位地址要从右往左存。于是就有了下面两种存取方式。 其实这两种方式没有孰优孰劣只是我们认知习惯有所不同 最终的设计不同说来这都是阿拉伯人的锅啊为什么数字高位非要在左边这也引起了著名的大小端之争。
因此字节序也就有了大端和小端的概念也形成了各自的阵营比如Windows、FreeBSD、Linux 是小端序Mac是大端序。其实大小端序并没有技术上的好坏之分。
小端序( Little-Endian)就是低位字节即小端字节、尾端字节存放在内存的低地址而高位字节即大端字节、头端字节存放在内存的高地址。
大端序(Big-Endian )就是高位字节即大端字节、头端字节存放在内存的低地址低位字节即小端字节、尾端字节存放在内存的高地址。
6.3 UTF-8
6.3.1 简介规则
Unicode还是UCS最初都是采用多字节定长编码由于没有兼容现有的 ASCII 标准的文件和软件新标准很难被推广于是兼容ASCII版本的UTF-8就诞生了。
UTF-88-bit Unicode Transformation Format是一种针对Unicode的可变长度字符编码是现代字符编码模型中的第三层 CEF 。它可以用一至四个字节对 Unicode 字符集中的所有有效编码点进行编码属于Unicode标准的一部分UTF-8 就是为了解决向后兼容 ASCII 码而设计Unicode 中前 128 个字符与 ASCII 码一一对应使用与 ASCII 码相同的二进制值的单个字节进行编码这使得原来处理 ASCII 字符的软件无须或只须做少部分修改即可继续使用。因此它逐渐成为电子邮件、网页及其他存储或发送文字优先采用的编码方式。
UTF-8需要兼容ASCII所以也需要有前缀码来控制前缀规则如下
如果首字节以 0 开头则是单字节编码即单个单字节码元如果首字节以 110 开头则是双字节编码即由两个单字节码元所组成的双码元序列如果首字节以 1110 开头则是三字节编码即由三个单字节码元所组成的三码元序列以此类推。 理论上UTF-8变长可以超过4个字节只是Unicode联盟规范上限是10FFFF所以UTF-8规则设计上也限制了大小。
6.3.2 程序算法
用文字不太好描述算法结构我们就直接来欣赏一下UTF-8鼻祖写的这段解析代码这是Ken 和 Rob 用一个晚上写出来的编解码算法代码非常简短精炼为了方便阅读我加了注释解读。
typedef
struct
{int cmask; //前缀码掩码int cval; //前缀码int shift; //移动位数long lmask; //Unicode值掩码long lval; //Unicode下限值
} Tab;static
Tab tab[]
{0x80, 0x00, 0*6, 0x7F, 0, /* 1 byte sequence */0xE0, 0xC0, 1*6, 0x7FF, 0x80, /* 2 byte sequence */0xF0, 0xE0, 2*6, 0xFFFF, 0x800, /* 3 byte sequence */0xF8, 0xF0, 3*6, 0x1FFFFF, 0x10000, /* 4 byte sequence */0xFC, 0xF8, 4*6, 0x3FFFFFF, 0x200000, /* 5 byte sequence */0xFE, 0xFC, 5*6, 0x7FFFFFFF, 0x4000000, /* 6 byte sequence */0, /* end of table */
};/**
* 把一个多字节序列转换为一个宽字符
*
* param p 存放计算后的unicode值
* param s 需要解析的UTF-8字节序列
* param n 字节长度
* return 解析的字节长度
*/
int mbtowc(wchar_t *p, char *s, size_t n)
{long l; int c0, c, nc; Tab *t;if(s 0) return 0;nc 0;//异常校验(可不用关注)if(n nc) return -1;//c0 此处备份一下首字节后续需要用到前缀码c0 *s 0xff;//l 保存 Unicode 结果l c0;/* 遍历tab从单字节结构-2字节结构-..依次检查找到对应tab */for(ttab; t-cmask; t) {//字节数1字节数和tab结构是对应的也就是当nc1时 tab结构是单字节nc2是tab是两字节nc;/* 判断前缀码跟当前的tab是否一致 如果一致计算最终unicode值并返回*/if((c0 t-cmask) t-cval) {//通过 Unicode有效值掩码移除高位前缀码得到最终unicode值l t-lmask;//异常校验if(l t-lval) return -1;//保存结果并反回*p l;return nc;}//异常校验if(n nc) return -1;//读取下个字节如果上面判断前缀码不一致说明需要再读取下个字节s;//计算有效位的值目的是去除UTF-8 编码从第二个字节开始的高两位10// 例如 s10101111、0x8010000000 计算结果是00101111,这样就去除了高位前缀10c (*s ^ 0x80) 0xFF;//异常校验if(c 0xC0) return -1;//重新计算unicode值根据UTF-8规则c只有低 6 位有效,所以通过移位把c填入到l的低6位l (l6) | c;}//返回异常return -1;
}6.3.3 容错性
通过上面的程序我们知道解析过程是一个字节一个字节往下处理的我们在传输过程中如果发生局部的字节错误、丢失或者中间有一个字节规则对不上会不会影响整个文本的解析
我们先来看下其他编码的容错情况从对于单字节的ASCII码来说丢失一个字节就丢失一个字符并不影响后续文本的内容比如Hello world丢失b2字节后内容是Hllo world少个e而已
我们再来看GB2312这种多字节编码如果丢失了b2字节那么整个文本都乱套了这是最糟糕的大部分多字节编码都有类似问题一旦出现错误可能导致整个文件都需要重传。 接下来我们看看UTF-8是如何避免这种“一颗老鼠屎坏了一锅粥”的情况UTF-8 的码元序列的第一个字节指明了后面所跟字节的个数比如首字节高位是0就表示单字节110表示总共两个字节1110表示三个字节依次类推除首字节之外后续字节都是10开头。所以UTF-8的前缀码具有很强的鲁棒性即使丢失、增加、改变个别字节也不会导致后续字符全部错乱这样的传递性、连锁性的错误问题。
7. Accept-Charset/Accept-Encoding/Accept-Language/Content-Type/Content-Encoding/Content-Language
在HTTP中与字符集和字符编码相关的消息头是Accept-Charset/Content-Type另外主区区分Accept-Charset/Accept-Encoding/Accept-Language/Content-Type/Content-Encoding/Content-Language Accept-Charset浏览器申明自己接收的字符集这就是本文前面介绍的各种字符集和字符编码如gb2312utf-8通常我们说Charset包括了相应的字符编码方案 Accept-Encoding浏览器申明自己接收的编码方法通常指定压缩方法是否支持压缩支持什么压缩方法gzipdeflate注意这不是只字符编码 Accept-Language浏览器申明自己接收的语言。语言跟字符集的区别中文是语言中文有多种字符集比如big5gb2312gbk等等 Content-TypeWEB服务器告诉浏览器自己响应的对象的类型和字符集。例如Content-Type: text/html; charset‘gb2312’ Content-EncodingWEB服务器表明自己使用了什么压缩方法gzipdeflate压缩响应中的对象。例如Content-Encodinggzip Content-LanguageWEB服务器告诉浏览器自己响应的对象的语言。
8. mysql字符集
一个线上BUG彻底搞懂MySQL字符集工作也快搞丢了 深入理解MySQL字符集