字符集和字符编码
一文参透字符编码的难题!
引子
在 python 中,处理字符串是常见任务,因为字符串编码问题,经常出现字符串乱码。
在 matplotlib 绘图时,text
对象,如 axes_title, axis_label,使用中文时,也会出现乱码的情况。但这是因为字体管理的原因,与 python 解析字符串时的乱码,有本质上的不同。
在 Python 中,有 2 种常用的字符串类型,分别为 str
和bytes
类型,其中 str
用来表示 Unicode 字符,bytes
用来表示二进制数据。str 类型和 bytes 类型之间就需要使用 encode() 和 decode() 方法进行转换。
Python encode()方法
encode() 方法为字符串类型(str)提供的方法,用于将 str 类型转换成 bytes 类型,这个过程也称为“编码”。
encode() 方法的语法格式如下:
str.encode([encoding="utf-8"][,errors="strict"])
Python decode()方法
和 encode() 方法正好相反,decode() 方法用于将 bytes 类型的二进制数据转换为 str 类型,这个过程也称为“解码”。
decode() 方法的语法格式如下:
bytes.decode([encoding="utf-8"][,errors="strict"])
字符集和字符编码(Charset & Encoding)
本节中的 python 代码都是基于python3.5,如果你的python版本不同,可能会有稍许差异。
相信大家一定碰到过,打开某个网页,却显示一堆乱码;一个文件在这台计算机上打开很正常,到另一台计算机上打开就成了一篇鸟语天书。如下图,好好的《琵琶行》就变成了…
基础知识
语言的本质
计算机的本质作用还是用于信息交流,这种交流包括人与人、人与计算机的交流,在计算机世界,交流信息的方式变成了计算机“文件”,途径有磁盘、网络。把要交流的信息打印出来就回归到了人与人之间交流的普通形态,这与书信交流是一样的。
信息交流依赖于语言和文字。
语言(language)是一个由一些声音和文字符号组成的集合,它是用于交流的系统,语言通常具有国家和地区的特点。柯林斯词典对Language的解释是:A language is a system of communication which consists of a set of sounds and written symbols which are used by the people of a particular country or region for talking or writing.
语言有两个基本组成要素:声音、文字符号。声音是为了口头交流的,文字符号是用于书面交流的。假设有甲、乙两个人,甲说话流利,但一字不识,他只能通过声音来与别人交流了,文字对于甲是没有意义的符号而已;乙早年读了很多数,但后天失音失聪,虽然此后声音对他已没有意义,但他仍然完全可以通过阅读、书写来交流
因为语言还有一个隐含的“国家或地区”的属性,也就让语言具有了欺骗性。实际上全人类都被“语言”欺骗了,尤其语言里的文字元素欺骗性更强。
想像一下:有一个美国人,他以前从没有接触过汉字,第一次教他认汉字,我们在黑板上写下“白 = black”,并教他“白”,“bái”,反复读5遍,在他的思维中,“白”就是“black”了。这与我们认读错别字是一样的效果,但他不会意识到这种所谓的错误。
如果在一张纸上写下“pain”四个字母组成的单词,问一个中国人,这个单词是什么意思?什么外语都没学的人会说“不知道!”,仅学过英语的会说“痛苦”,而仅学过法语的会说“面包”。而对于学过英语和法语的人,就需要告诉他"pain"是哪个国家的语言了!说“不知道”没有错,因为对他来说“pain”就是无意义的符号,说“痛苦”的,说“面包”的也都没有错。这就是语言的哲学本质!(法语的pain指“面包”,On veut du pain! = We want bread!英语的pain指“痛苦”,相当于法语的douleur。J’ai une douleur au pied gauche. = I have a pain in my left foot.)。
今天我们重点关注计算机处理、交换信息时如何处理语言的文字元素。
文字的本质
文字不过是具有系统性的符号,这种系统性就是每种文字是按照一定的规范由一定的字符组成的,如果我们强行将“ЇЯ”这个字符添加进汉字大家庭,说这是一个汉字,就违反了汉字的组字和书写规范。
字符(character)和字符集(charset)
“字符”一词有两个含义,“字”,“符”。所有写出来、画出来、打印出来的图形都是符号,这是广义的符号的概念。字符则是符号的一个子集,“字”是文字,文字是语言的一个元素,“字符”是人类语言中用到的符号的集合。
字符(character)是一个字母、数字或者其它符号(比如“,。”),用于书写和打印的符号的集合。字符是各种文字和符号的总称(文字的本质也是符号),包括各国文字、标点符号、图形符号、数字等。柯林斯词典:A character is a letter, number, or other symbol that is written or printed. 请记住字符的三个要点:
- 本质也是“符号”
- 目的是为了书写和打印
- 符是组成文字的最基本的元素。
字符集(Charset):是一个系统支持的所有抽象字符的集合。比如,简体汉字及其书写、打印时用到的各种符号组成的集合—“简体汉字字符集”。美国英语和键盘输入用到的各种字母、符号集合,主要包括控制字符(回车键、退格、换行键等);可显示字符(英文大小写字符、阿拉伯数字和西文符号),就构成了“美国信息交换标准字符集”。
显然,“字符character”是一个抽象的概念,就是各种语言中使用的文字和符号的抽象称呼。你可以画一张驱鬼符,虽然它是一个符号,但它不属于任何一种语言文字体系中的符号,就不能称之为“字符”。
字符集则是带有主观色彩的概念,有具体使用和来源范围的字符集合,比如美国国家标准字符集、简体中文汉字字符集等。
字符集中的字符有两个属性:
- 一是抽象的视觉符号,如“H”,它就是一个两竖中间有一横的这么一个图形符号;“口”就是这么一个方框形状的符号。
- 二是它在语言环境下的字面本义(literal),“H”ASCII字符集中代表英语字母表中第8个字母的大写,有它的读音;“口”在中文简体中指五官之一,也有它的读音。
The literal sense of a word or phrase is its most basic sense.单词或短语的字面意思是其最基本的意义。
字符编码(character encoding)
计算机其实是一个“强大”的文盲,它只认识两个字符 1和0,它要存储和读取信息都是使用这两个数字进行编码。人们要与计算机进行交流就需要把要交换的信息转换为计算机懂得的类似于“010001011110”这样的编码。
在计算机世界里,最基本的交流对象就是语言文字,人把要表达的意思用语言文字反映出来,翻译成全部由0、1组成的编码符号后提交给计算机,计算机把这些编码存储(1在磁盘上存为一个小凸点,0在磁盘上存为一个小凹点)起来,当人们需要时,计算机再把这些“0、1”编码读出来,翻译成人类的语言文字打印到屏幕或纸张上呈现给人们。这就是计算机与人交流的过程和模式:
- 人—信息—这个人的语言文字符号—0、1组成的编码—交给计算机存储为文件
- 计算机读取存储的01编码的文件—转换为这个人的语言文字符号—打印(屏幕或纸张)—呈现给人
计算机中储存的信息都是用二进制数表示的,而我们在屏幕上看到的英文、汉字等字符是二进制数转换之后的结果。通俗的说,按照何种规则将字符存储在计算机中,如’a’用什么表示,称为"编码";反之,将存储在计算机中的二进制数解析显示出来,称为"解码(decode)",如同密码学中的加密和解密。
显然这需要一个编码和解码规则,在解码过程中,如果使用了错误的解码规则,则导致’a’解析成’b’或者乱码。在开头的例子中,“浔阳江头夜送客,枫叶荻花秋瑟瑟。”打印到屏幕时成了“mT邶l_Y4Y?[?g檻{侒鋟_t_0”,肯定是读取时使用了与存储时不同的编码导致的。
使用计算机的人若都使用一套自己的胡乱编码方法,则计算机世界就是混沌的,计算机世界的信息将无法交流,显然需要有统一的编码规则。就象发收电报,若发电报的人用一套编码本、收电报的人用另一套解码本,两个人根本不知道对方的编码方式,这封电报就完全失去了意义。
所以,随着计算机使用范围的扩大,不同国家和地区为了本地语言在计算机世界的交流,都对本国语言字符进行了收集整理(某个字符集),并形成自己的编码规则。
没有编码的字符集是没有什么意义和用处的,所以,每个形成的字符集都对其中的字符按照一定的规则进行了编码,这就是“字符编码(character encoding)”的概念。
重要的事再说一遍:字符集就是一个大箱子,把某种语言的文字符号全部绘制出来存放在里面,这个箱子就是一个字符集合;制定一套规则,给箱子中每个符号一个特定的编码,大家约定,在我们国家的计算机世界里都用这个编码代表每个文字符号,这就形成了字符编码。
字符集和字符编码总是相对应的。
因为计算机技术的发展和信息交流需要的变化,有可能一套字符集有几种编码方式,比如Unicode字符集就有utf-8,utf-16,utf-32等多种编码方式。参见“Unicode字符集和utf编码”。
字符编码(Character Encoding):是一套法则,使用该法则能够对自然语言的字符的一个集合(如字母表或音节表),与其他东西的一个集合(如号码或电脉冲)进行配对。即在符号集合与数字系统之间建立对应关系,它是信息处理的一项基本技术。通常人们用符号集合(一般情况下就是文字)来表达信息。而以计算机为基础的信息处理系统则是利用元件(硬件)不同状态的组合来存储和处理信息的。元件不同状态的组合能代表数字系统的数字,因此字符编码就是将符号转换为计算机可以接受的数字系统的数,称为数字代码。
String和Character的literal
Character,字符,语言文字中用到的符号。用这个词,表示我们要讨论和关注的的是字符的“符号”层面的意义。比如字符“b”、“白”,请更多的关注它就是个符号。
String,字符串,多个字符组成的串儿(废话!),但s=’b’,s=’白’时,虽然它只有一个字符,没有串,但它的含义与character是有差异的。当使用string、字符串时,表示我们要关注的是它的“串”,而不能仅停留在它的“符号”层面。Sting字符串就有两方面的意义:一是包含的字符的本义,比如“白”,这里的“白”表示颜色,是中文语言中的一个字符;“b”是英语字母表中的第二个字母的小写;另一方面,字符串在计算机世界中可能还有另一个意义,比如:s=b’xe7x99xbd’,这时的s看起来也是一个字符串,它的字面本义就是这样一些字符的组合串:b、、x、e、b、d、7、9。但在计算世界中它还是“白”字的utf-8编码。
在Python中完全可以从s=b’xe7x99xbd’得到“白”字,如下图:
瞧,它既可输出“b’xe7x99xbd’”,又可以输出“白”字。不仅如此,s2=b’xffxfe}v’,我们竟然也得到了“白”字。
内码和交换码
字符编码有“内码”和“交换码”两种。简单地说,内码就是使用二进制,计算机能直接识别的“0、1”两个字符编的码就是内码。不直接采用二进制编码,而采用统一编码机制编码的就是交换码,Unicode统一编码的方式就是交换码。
这就像有一个西班牙人到中国来,一时找不到既懂西班牙语,又懂中文的翻译,但能找到西班牙语—英语的翻译,又能找到英语—中文的翻译,好吧,就找两个翻译,翻译的过程是这样的:
西班牙语---英语---中文
或者反过来:
中文---英语---西班牙语。
这里,英语就充当了交换码的角色。
登录 http://unicodelookup.com/ 可以查询Unicode字符集中每个字符的编码:
8进制、16进制的编码本质是内码,因为它只是二进制编码的一种表达方式,这种进制的转换基本不受计算机系统及其语言的影响。
常用字符集和字符编码
常见字符集有:ASCII字符集、GB2312字符集、BIG5字符集、GB18030字符集、Unicode字符集等。计算机要准确的处理各种字符集文字,需要进行字符编码,以便计算机能够识别和存储各种文字。
ASCII字符集&编码
ASCII(American 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的最大缺点是只能显示26个基本拉丁字母、阿拉伯数目字和英式标点符号,因此只能用于显示现代美国英语(而且在处理英语当中的外来词如naïve、café、élite等等时,所有重音符号都不得不去掉,即使这样做会违反拼写规则)。而EASCII虽然解决了部份西欧语言的显示问题,但对更多其他语言依然无能为力。因此现在的苹果电脑已经抛弃ASCII而转用Unicode。
GBXXXX字符集&编码
计算机发明后,很长一段时间只应用于美国及西方一些发达国家,ASCII能够很好满足用户的需求。但是当“秦”的后裔也有了计算机之后,为了显示中文,必须设计一套编码规则用于将汉字转换为计算机可以接受的数字系统的数。
天朝的专家把那些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编码:1981年5月1日发布的简体中文汉字编码国家标准。GB2312对汉字采用双字节编码,收录7445个图形字符,其中包括6763个汉字。
登录http://www.qqxiuzi.cn/bianma/guobiaoma.php,对汉字的编码作了简要说明:
- 国标码是汉字的国家标准编码,目前主要有GB2312、GBK、GB18030三种:
- GB2312编码方案于1980年发布,收录汉字6763个,采用双字节编码。
- GBK编码方案于1995年发布,收录汉字21003个,采用双字节编码。
- GB18030编码方案于2000年发布第一版,收录汉字27533个;2005年发布第二版,收录汉字70000余个,以及多种少数民族文字。GB18030采用单字节、双字节、四字节分段编码。
输入“白”,它会给出这三种编码方案中白字的编码:
- GB2312编码:B0D7
- GBK编码:B0D7
- GB18030编码:B0D7
因为是向后兼容的,所以三种方案“白”字的编码是一样的。
由于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。
“cp936”是gbk编码的别名,在Python中print(file)会返回文件对象的路径、编码等信息,如果文件是gbk编码的,总是显示为“cp936”,如下图所示:
GB 18030,全称:国家标准GB 18030-2005《信息技术 中文编码字符集》,是中华人民共和国现时最新的内码字集,是GB 18030-2000《信息技术 信息交换用汉字编码字符集 基本集的扩充》的修订版。与GB 2312-1980完全兼容,与GBK基本兼容,支持GB 13000及Unicode的全部统一汉字,共收录汉字70244个。GB 18030主要有以下特点:
- 与UTF-8相同,采用多字节编码,每个字可以由1个、2个或4个字节组成。
- 编码空间庞大,最多可定义161万个字符。
- 支持中国国内少数民族的文字,不需要动用造字区。
- 汉字收录范围包含繁体汉字以及日韩汉字
本规格的初版由中华人民共和国信息产业部电子工业标准化研究所起草,由国家质量技术监督局于2000年3月17日发布。现行版本为国家质量监督检验总局和中国国家标准化管理委员会于2005年11月8日发布,2006年5月1日实施。此规格为在中国境内所有软件产品支持的强制规格。
BIG5字符集&编码
Big5,又称为大五码或五大码,是使用繁体中文(正体中文)社区中最常用的电脑汉字字符集标准,共收录13,060个汉字。中文码分为内码及交换码两类,Big5属中文内码,知名的中文交换码有CCCII、CNS11643。Big5虽普及于台湾、香港与澳门等繁体中文通行区,但长期以来并非当地的国家标准,而只是业界标准。倚天中文系统、Windows等主要系统的字符集都是以Big5为基准,但厂商又各自增加不同的造字与造字区,派生成多种不同版本。2003年,Big5被收录到CNS11643中文标准交换码的附录当中,取得了较正式的地位。这个最新版本被称为Big5-2003。
Big5码是一套双字节字符集,使用了双八码存储方法,以两个字节来安放一个字。第一个字节称为"高位字节",第二个字节称为"低位字节"。"高位字节"使用了0x81-0xFE,"低位字节"使用了0x40-0x7E,及0xA1-0xFE。在Big5的分区中:
0x8140-0xA0FE | 保留给用户自定义字符(造字区) |
---|---|
0xA140-0xA3BF | 标点符号、希腊字母及特殊符号,包括在0xA259-0xA261,安放了九个计量用汉字:兙兛兞兝兡兣嗧瓩糎。 |
0xA3C0-0xA3FE | 保留。此区没有开放作造字区用。 |
0xA440-0xC67E | 常用汉字,先按笔划再按部首排序。 |
0xC6A1-0xC8FE | 保留给用户自定义字符(造字区) |
0xC940-0xF9D5 | 次常用汉字,亦是先按笔划再按部首排序。 |
0xF9D6-0xFEFE | 保留给用户自定义字符(造字区) |
Unicode字符集&UTF编码
当计算机传到世界各个国家时,为了适合当地语言和字符,设计和实现类似GB232/GBK/GB18030/BIG5的编码方案。这样各搞一套,在本地使用没有问题,一旦出现在网络中,由于不兼容,互相访问就出现了乱码现象。
为了解决这个问题,类似于秦统一文字一个伟大的构想产生了——Unicode。Unicode字符集及其编码系统为表达任意语言的任意字符而设计。它使用4字节的数字来表达每个字母、符号,或者表意文字(ideograph)。每个数字代表唯一的至少在某种语言中使用的符号。(并不是所有的数字都用上了,但是总数已经超过了65535,所以2个字节的数字是不够用的。)被几种语言共用的字符通常使用相同的数字来编码,除非存在一个在理的语源学(etymological)理由使不这样做。不考虑这种情况的话,每个字符对应一个数字,每个数字对应一个字符。即不存在二义性。不再需要记录"模式"了。U+0041总是代表’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是三种该字符集下的几种字符编码方案。
UCS & UNICODE
通用字符集(Universal Character Set,UCS)是由ISO制定的ISO 10646(或称ISO/IEC 10646)标准所定义的标准字符集。历史上存在两个独立的尝试创立单一字符集的组织,即国际标准化组织(ISO)和多语言软件制造商组成的统一码联盟。前者开发的 ISO/IEC 10646 项目,后者开发的统一码项目。因此最初制定了不同的标准。
1991年前后,两个项目的参与者都认识到,世界不需要两个不兼容的字符集。于是,它们开始合并双方的工作成果,并为创立一个单一编码表而协同工作。从Unicode 2.0开始,Unicode采用了与ISO 10646-1相同的字库和字码;ISO也承诺,ISO 10646将不会替超出U+10FFFF的UCS-4编码赋值,以使得两者保持一致。两个项目仍都存在,并独立地公布各自的标准。但统一码联盟和ISO/IEC JTC1/SC2都同意保持两者标准的码表兼容,并紧密地共同调整任何未来的扩展。在发布的时候,Unicode一般都会采用有关字码最常见的字型,但ISO 10646一般都尽可能采用Century字型。
UTF-32
上述使用4字节的数字来表达每个字母、符号,或者表意文字(ideograph),每个数字代表唯一的至少在某种语言中使用的符号的编码方案,称为UTF-32。UTF-32又称UCS-4是一种将Unicode字符编码的协定,对每个字符都使用4字节。就空间而言,是非常没有效率的。
这种方法有其优点,最重要的一点就是可以在常数时间内定位字符串里的第N个字符,因为第N个字符从第4×Nth个字节开始。虽然每一个码位使用固定长定的字节看似方便,它并不如其它Unicode编码使用得广泛。
UTF-16
尽管有Unicode字符非常多,但是实际上大多数人不会用到超过前65535个以外的字符。因此,就有了另外一种Unicode编码方式,叫做UTF-16(因为16位 = 2字节)。UTF-16将0–65535范围内的字符编码成2个字节,如果真的需要表达那些很少使用的超过这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编码方式还有一些其他不明显的缺点。不同的计算机系统会以不同的顺序保存字节。这意味着字符U+4E2D在UTF-16编码方式下可能被保存为4E 2D或者2D 4E,这取决于该系统使用的是大尾端(big-endian,二进制资料存放的次序)还是小尾端(little-endian)。只要文档没有离开你的计算机,它还是安全的——同一台电脑上的不同程序使用相同的字节顺序(byte order)。但是当我们需要在系统之间传输这个文档的时候,也许在万维网中,我们就需要一种方法来指示当前我们的字节是怎样存储的。不然的话,接收文档的计算机就无法知道这两个字节4E 2D表达的到底是U+4E2D还是U+2D4E。
为了解决这个问题,多字节的Unicode编码方式定义了一个"字节顺序标记(Byte Order Mark)",它是一个特殊的非打印字符,你可以把它包含在文档的开头来指示你所使用的字节顺序。对于UTF-16,字节顺序标记是U+FEFF。如果收到一个以字节FF FE开头的UTF-16编码的文档,你就能确定它的字节顺序是单向的(one way)的了;如果它以FE FF开头,则可以确定字节顺序是反向的了。
UTF-8
UTF-8(8-bit Unicode Transformation Format)是针对Unicode字符的一种可变长度的字符编码(定长码),也是一种前缀码。它可以用来表示Unicode标准中的任何字符,且其编码中的第一个字节仍与ASCII兼容,这使得原来处理ASCII字符的软件无须或只须做少部份修改,即可继续使用。因此,它逐渐成为电子邮件、网页及其他存储或传送文字的应用中,优先采用的编码。互联网工程工作小组(IETF)要求所有互联网协议都必须支持UTF-8编码。
UTF-8使用一至四个字节为每个字符编码:
- 128个US-ASCII字符只需一个字节编码(Unicode范围由U+0000至U+007F)。
- 带有附加符号的拉丁文、希腊文、西里尔字母、亚美尼亚语、希伯来文、阿拉伯文、叙利亚文及它拿字母则需要二个字节编码(Unicode范围由U+0080至U+07FF)。
- 其他基本多文种平面(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)复杂度(空间复杂度)的操作 — 即,串越长,则需要更多的时间来定位特定的字符。同时,还需要位变换来把字符编码成字节,把字节解码成字符。
HTTP中的字符集和字符编码消息头
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:浏览器申明自己接收的字符集,这就是本文前面介绍的各种字符集和字符编码,如gb2312,utf-8(通常我们说Charset包括了相应的字符编码方案);
Accept-Encoding:浏览器申明自己接收的编码方法,通常指定压缩方法,是否支持压缩,支持什么压缩方法(gzip,deflate),(注意:这不是只字符编码);
Accept-Language:浏览器申明自己接收的语言。语言跟字符集的区别:中文是语言,中文有多种字符集,比如big5,gb2312,gbk等等;
Content-Type:WEB服务器告诉浏览器自己响应的对象的类型和字符集。例如:Content-Type: text/html; charset=‘gb2312’
Content-Encoding:WEB服务器表明自己使用了什么压缩方法(gzip,deflate)压缩响应中的对象。例如:Content-Encoding:gzip
器申明自己接收的编码方法,通常指定压缩方法,是否支持压缩,支持什么压缩方法(gzip,deflate),(注意:这不是只字符编码);
Accept-Language:浏览器申明自己接收的语言。语言跟字符集的区别:中文是语言,中文有多种字符集,比如big5,gb2312,gbk等等;
Content-Type:WEB服务器告诉浏览器自己响应的对象的类型和字符集。例如:Content-Type: text/html; charset=‘gb2312’
Content-Encoding:WEB服务器表明自己使用了什么压缩方法(gzip,deflate)压缩响应中的对象。例如:Content-Encoding:gzip
Content-Language:WEB服务器告诉浏览器自己响应的对象的语言。
–End–