咱们知道,计算机只能识别诸如 0101 这样的二进制数,因而人们必须以二进制数据与计算机进行交互,或者先将人类使用的字符按必定规则转换为二进制数。
那什么是字符呢?在计算机领域,咱们把诸如文字、标点符号、图形符号、数字等统称为字符。而由字符组成的集合则成为字符集,字符集因为包含字符的多少与异同而造成了各类不一样的字符集。
咱们知道,全部字符在计算机中都是以二进制来存储的。那么一个字符究竟由多少个二进制位来表示呢?这就涉及到字符编码的概念了,好比一个字符集有 8 个字符,那么用 3 个二进制位就能够彻底表示该字符集的全部字符,也即每一个字符用 3 个二进制位进行编码。
咱们规定字符编码必须完成以下两件事:
(1)规定一个字符集中的字符由多少个字节表示
(2)制定该字符集的字符编码表,即该字符集中每一个字符对应的(二进制)值。
1. ASCII 码:
上个世纪 60 年代,美国制定了一套字符编码标准,对英语字符与二进制位之间的关系,作了统一规定。这被称为 ASCII 码,一直沿用至今。
ASCII(American Standard Code for Information Interchange),是一种字符编码标准,它的字符集为英文字符集,它规定字符集中的每一个字符均由一个字节表示,指定了字符表编码表,称为 ASCII 码表。它已被国际标准化组织定义为国际标准,称为 ISO646 标准。
ASCII 码一共规定了 128 个字符的编码,好比空格“SPACE”是 32(二进制00100000),大写的字母 A是 65(二进制01000001)等。这 128 个符号(包括32个不能打印出来的控制符号),只占用了一个字节的后面 7 位,最前面的 1 位统一规定为 0。这种采用一个字节来编码 128 个字符的 ASCII 码称为标准 ASCII 码或者基础 ASCII 码。
可是,因为标准 ASCII 字符集字符数目有限,在实际应用中每每没法知足要求。为此,国际标准化组织又制定了 ISO 2022 标准,它规定了在保持与 ISO646 兼容的前提下将 ASCII 字符集扩充为 8 位代码的统一方法。 ISO 陆续制定了一批适用于不一样地区的扩充 ASCII 字符集,每种扩充 ASCII 字符集分别能够扩充 128 个字符,这些扩充字符的编码均为高位为 1 的 8 位代码(即十进制数 128~255 ),称为扩展 ASCII 码。
可是须要注意,各类扩展 ASCII 码除了编码为 0~127 的字符外,编码为 128~255 的字符并不相同。好比,130 在法语编码中表明了 é,在希伯来语编码中却表明了字母 Gimel (?),在俄语编码中又会表明另外一个符号。
2. ANSI编码标准
标准 ASCII 码和扩展 ASCII 码知足了西语国家的需求,可是,随着计算机在世界范围内的普及,对于亚洲国家,如中日韩等国来讲,他们使用的符号不少,ASCII 字符编码标准远远不能知足其须要,因而这些国家便针对本国的字符集指定了相应的字符编码标准,如 GB23十二、BIG五、JIS 等仅适用于本国字符集的编码标准。
这些字符编码标准统称为 ANSI 编码标准,这些 ANSI 编码标准有一些共同的特色:
(1)每种 ANSI 字符集只规定本身国家或地区使用的语言所需的'字符',好比简体中文编码标准 GB-2312 的字符集中就不会包含韩国人的文字。
(2)ANSI 字符集的空间都比 ASCII 要大不少,一个字节已经不够,绝大多数 ANSI 编码标准都使用多个字节来表示一个字符。
(3)ANSI 编码标准通常都会兼容 ASCII 码。
这里要特别提一下我国的几种字符编码标准:GB23十二、GBK、GB18030。
字符必须编码后才能被计算机处理。计算机使用的默认编码方式就是计算机的内码。早期的计算机使用 7 位的 ASCII 编码(标准 ASCII 编码),为了处理汉字,程序员设计了用于简体中文的 GB2312 和用于繁体中文的 big5。
GB2312(1980年) 一共收录了 7445 个字符,包括 6763 个汉字和 682 个其它符号。汉字区的内码范围高字节从 B0-F7,低字节从 A1-FE,占用的码位是 72*94=6768。其中有 5 个空位是 D7FA-D7FE。
GB2312 支持的汉字太少。1995年的汉字扩展规范 GBO1.0 收录了 21886 个符号,它分为汉字区和图形符号区。汉字区包括 21003 个字符。2000 年的 GB18030 是取代 GBO1.0 的正式国家标准。该标准收录了 27484 个汉字,同时还收录了藏文、蒙文、维吾尔文等主要的少数民族文字。如今的 PC 平台必须支持 GB18030,对嵌入式产品暂不做要求。因此手机、MP3通常只支持 GB2312。
例如,在 Windows 中打开记事本,"另存为"对话框的"编码"下拉框中有一项 ANSI 编码,ANSI 是默认的编码方式。对于英文文件是 ASCII 编码,对于简体中文文件是 GB2312 编码(只针对Windows简体中文版,若是是繁体中文版会采用 Big5 码),在日文操做系统下,ANSI 编码表明 JIS 编码,其余语言的系统的状况相似。
3. Unicode、UCS 和 UTF
可是随着互联网的兴起,问题又出现了。因为 ANSI 码的第一个特色:各个国家或地区在编制本身的 ANSI 码时并未考虑到其余国家或地区的 ANSI 码,致使编码空间有重叠,好比:汉字'中'的编码是[0xD6,0xD0],这个编码在其余国家的 ANSI 编码标准中则不必定就是该编码了。因而,同一个二进制数字能够被解释成不一样的符号。所以,要想打开一个文本文件,就必须知道它的编码方式,不然用错误的编码方式解读,就会出现乱码。这样一来当在不一样ANSI编码标准之间进行信息交换和显示的时候,乱码就不可避免了。
(1)Unicode
能够想象,若是有一种编码,将世界上全部的符号都归入其中,每个符号都给予一个独一无二的编码,那么乱码问题就会消失。这就是 Unicode,就像它的名字所表示的,这是一种全部符号的编码。
Unicode 是 Universal Multiple-Octet Coded Character Set 的缩写,中文含义是"通用多八位编码字符集"。它是由一个名为 Unicode 学术学会(Unicode。org)的机构制订的字符编码标准,Unicode 目标是将世界上绝大多数国家的文字、符号都编入其字符集,它为每种语言中的每一个字符设定了统一而且惟一的二进制编码,以知足跨语言、跨平台进行文本转换、处理的要求,以达到支持现今世界各类不一样语言的书面文本的交换、处理及显示的目的,使世界范围人们经过计算机进行信息交换时达到畅通自如而无障碍。
因为一个 Unicode 字符用多个字节表示。这样 Unicode 编码在不一样平台存储时就要注意其字节序了。好比:采用标准Unicode编码的'中'在 X86 平台上(big endian)的存储就是 '2D4E',而在 SPARC Solaris 上(little endian)的存储则是 '4E2D'。
(2) UCS
那什么又是 UCS 呢,它与 Unicode 有何关系?
历史上,有两个独立创立统一字符集的尝试。一个是国际标准化组织(ISO)的 ISO10646 项目, 另外一个是由(一开始大可能是美国的)多语言软件制造商组成的协会(unicode.org)组织的 Unicode 项目。 幸运的是, 1991 年先后, 两个项目的参与者都认识到, 世界不须要两个不一样的统一字符集。 它们合并双方的工做成果, 并为创立一个统一编码表而协同工做。如今,两个项目仍都存在并独立地公布各自的标准, 但 Unicode 协会和 ISO/IEC JTC1/SC2 都赞成保持 Unicode 和 ISO10646 标准的码表兼容, 并紧密地共同调整任何将来的扩展。
国际标准 ISO10646 定义了通用字符集 (Universal Character Set) UCS。 UCS 是全部其余字符集标准的一个超集。 它保证与其余字符集是双向兼容的。 就是说, 若是你将任何文本字符串翻译到 UCS 格式, 而后再翻译回原编码, 你不会丢失任何信息。
ISO10646 定义了一个 31 位的字符集(4 个字节)。然而,在这巨大的编码空间中,迄今为止只分配了前 65534 个码位 (0x0000 到 0xFFFD)。 这个 UCS 的 16 位子集称为基本多语言面(Basic Multilingual Plane,BMP)。将被编码在 16 位 BMP 之外的字符都属于很是特殊的字符(好比象形文字),且只有专家在历史和科学领域里才会用到它们。按当前的计划, 未来也许不再会有字符被分配到从 0x000000 到 0x10FFFF 这个覆盖了超过 100 万个潜在的将来字符的 21 位的编码空间之外去了。ISO10646-1 标准第一次发表于 1993 年, 定义了字符集与 BMP 中内容的架构。定义 BMP 之外的字符编码的第二部分 ISO 10646-2 正在准备中, 但也许要过好几年才能完成。 新的字符仍源源不断地加入到 BMP 中, 但已经存在的字符是稳定的且不会再改变了。
UCS 不只给每一个字符分配一个代码, 并且赋予了一个正式的名字。 表示一个 UCS 值的十六进制数, 一般在前面加上 "U+", 就象 U+0041 表明字符"拉丁大写字母A"。UCS 字符 U+0000 到 U+007F 与 US-ASCII(ISO646) 是一致的, U+0000 到 U+00FF 与 ISO 8859-1(Latin-1) 也是一致的。从 U+E000 到 U+F8FF, 已经 BMP 之外的大范围的编码是为私用保留的。
Unicode 字符编码标准与 ISO10646 的通用字符集(Universal Character Set,UCS)概念相对应,目前的用于实用的 Unicode 版本对应于 UCS-2,即便用 16 位来表示一个 Unicode 字符。也就是每一个字符占用 2 个字节。这样理论上一共最多能够表示 65536(2 的 16 次方) 个字符。基本知足各类语言的使用。
实际上目前版本的 Unicode 还没有填充满这 16 位编码,保留了大量空间做为特殊使用或未来扩展。将来版本会扩充到 ISO 10646-1 实现级别 3,即涵盖 UCS-4 的全部字符。UCS-4 是一个更大的还没有填充彻底的31位字符集,加上恒为 0 的首位,共需占据 32 位,即 4 字节。理论上最多能表示 2147483648(2的31次方)个字符,彻底能够涵盖一切语言所用的符号。
因为Unicode 编码标准与 UCS 编码标准是相互兼容的,为了方便叙述,下面把两者做为一个统一编码标准来叙述。
(3)UTF
Unicode(UCS)只是一个字符集,它只规定了符号的二进制代码,却没有规定这个二进制代码应该如何存储。
好比,汉字“严”的 Unicode(UCS)码是十六进制数 4E25,转换成二进制数足足有 15 位(100111000100101),也就是说这个符号的表示至少须要 2 个字节。表示其余更大的符号,可能须要 3 个字节或者 4 个字节,甚至更多。
这里就有两个严重的问题,第一个问题是,如何才能区别 Unicode 和 ASCII?计算机怎么知道三个字节表示一个符号,而不是分别表示三个符号呢?第二个问题是,咱们已经知道,英文字母只用一个字节表示就够了,若是unicode统一规定,每一个符号用三个或四个字节表示,那么每一个英文字母前都必然有二到三个字节是0,这对于存储来讲是极大的浪费,文本文件的大小会所以大出二三倍,这是没法接受的。
为了解决这些问题,就出现了 UTF。
UTF(Unicode Translation Format),它是 Unicode (UCS)的实现(或存储)方式,称为 Unicode 转换格式。Unicode 的实现方式不一样于编码方式。一个字符的 Unicode 编码是肯定的。可是在实际传输过程当中,因为不一样系统平台的设计不必定一致,以及出于节省空间的目的,对 Unicode 编码的实现方式有所不一样。
UTF 有三种实现方式:
UTF-16:其自己就是标准的 Unicode 编码方案,又称为 UCS-2,它固定使用 16 bits(两个字节)来表示一个字符。
UTF-32:又称为UCS-4,它固定使用32 bits(四个字节)来表示一个字符。
UTF-8:最普遍的使用的 UTF 方案,UTF-8 使用可变长度字节来储存 Unicode 字符,例如 ASCII 字母继续使用 1 字节储存,重音文字、希腊字母或西里尔字母等使用 2 字节来储存,而经常使用的汉字就要使用 3 字节。辅助平面字符则使用 4 字节。UTF-8 更便于在使用 Unicode 的系统与现存的单字节的系统进行数据传输和交换。与前两个方案不一样:UTF-8 以字节为编码单元,没有字节序的问题。
UTF 有三种方案,那么如何在接收数据和存储数据时识别数据采用的是哪一个方案呢?
Unicode(UCS)规范中推荐的标记字节顺序的方法是 BOM。BOM 是 Byte order Mark。
在 UCS 编码中有一个叫作“ZERO WIDTH NO-BREAK SPACE”(零宽度非换行空格)的字符,它的编码是 FEFF。而 FFFE 在 UCS 中是不存在的字符,因此不会出如今实际传输中。UCS 规范建议咱们在传输字节流前,先传输字符“ZERO WIDTH NO-BREAK SPACE”。这样若是接收者收到 FEFF,就代表这个字节流是 Big-Endian 的;若是收到 FFFE,就代表这个字节流是 Little-Endian 的。所以字符“ZERO WIDTH NO-BREAK SPACE”又被称做 BOM。
UTF-8 不须要 BOM 来代表字节顺序,但能够用 BOM 来代表编码方式。字符“ZERO WIDTH NO-BREAK SPACE”的 UTF-8 编码是 EF BB BF。因此若是接收者收到以 EF BB BF 开头的字节流,就知道这是 UTF-8 编码了。Windows 就是使用 BOM 来标记文本文件的编码方式的。
这样根据识别前面的“ZERO WIDTH NO-BREAK SPACE”字符便可识别编码方案,字节流中前几个字节所表示的编码方式以下:程序员
EF BB BF网络 |
UTF-8 架构 |
FE FF编码 |
UTF-16/UCS-2, little endianspa |
FF FE操作系统 |
UTF-16/UCS-2, big endian 翻译 |
FE FF 00 00设计 |
UTF-32/UCS-4, little endiancode |
00 00 FF FEorm |
UTF-32/UCS-4, big-endian |
在微软公司Windows XP操做系统附带的记事本中,“另存为”对话框能够选择的四种编码方式除去非 Unicode 编码的 ANSI 外,其他三种“Unicode”、“Unicode big endian”和“UTF-8”分别对应 UTF-16 小端(BOM)、UTF-16 大端(BOM)和 UTF-8 这三种实现方式。
另外,内码是指操做系统内部的字符编码。早期操做系统的内码是与语言相关的。目前 Windows 的内核已经支持 Unicode 字符集,这样在内核上能够支持全世界全部的语言文字。可是因为现有的大量程序和文档都采用了某种特定语言的编码,例如 GBK,Windows 不可能不支持现有的编码,而所有改用 Unicode。因而 Windows 就使用代码页(code page)来适应各个国家和地区不一样的字符集。
而所谓代码页(code page)就是针对一种语言文字的字符编码。例如 GBK 的 code page 是 CP936,BIG5 的 code page 是 CP950,GB2312 的 code page 是 CP20936。
微软通常将默认代码页指定的编码说成是内码。默认代码页指的是:默认用什么编码来解释字符。例如 Windows 的记事本打开了一个文本文件,里面的内容是字节流:BA、BA、D七、D6。Windows 应该去怎么解释它呢?
是按照 Unicode 编码解释、仍是按照 GBK 解释、仍是按照 BIG5 解释,仍是按照 ISO8859-1 去解释?若是按 GBK 去解释,就会获得“汉字”两个字。按照其它编码解释,可能找不到对应的字符,也可能找到错误的字符。所谓“错误”是指与文本做者的本意不符,这时就产生了乱码。
答案是 Windows 按照当前的默认代码页去解释文本文件里的字节流。默认代码页能够经过控制面板的区域选项设置。记事本的另存为中有一项 ANSI,其实该项就是指按照默认代码页的编码方法保存。
注1:Unicode 编码转换为 UTF-8 编码的方法
UTF-8 就是以 8 位为单元对 Unicode 进行编码。下面是 Unicode 和 UTF-8 转换的规则:
Unicode |
UTF-8 |
0000 - 007F |
0xxxxxxx |
0080 - 07FF |
110xxxxx 10xxxxxx |
0800 - FFFF |
1110xxxx 10xxxxxx 10xxxxxx |
例如“汉”字的Unicode编码是 6C49。6C49 在 0800-FFFF 之间,因此确定要用 3 字节模板了:1110xxxx 10xxxxxx 10xxxxxx。将 6C49 写成二进制是:0110 110001 001001, 用这个比特流依次代替模板中的 x,获得:11100110 10110001 10001001,即 E6 B1 89。
注2:当一个软件打开一个文本时,它要作的第一件事是决定这个文本到底是使用哪一种字符集的哪一种编码保存的。软件通常采用三种方式来决定文本的字符集和编码:检测文件头标识,提示用户选择,根据必定的规则猜想。
最标准的途径是检测文本最开头的几个字节。
例如,当你在 Windows 的记事本里新建一个文件,输入“联通”两个字以后,保存,关闭,而后再次打开,你会发现这两个字已经消失了,代之的是几个乱码。当你新建一个文本文件时,记事本的编码默认是 ANSI(表明系统默认编码,在中文系统中是 GB 系列编码)。在这种编码下,"联通"的内码是:
c1 1100 0001
aa 1010 1010
cd 1100 1101
a8 1010 1000
注意,第一二个字节、第三四个字节的起始部分的都是"110"和"10",正好与 UTF-8 规则里的两字节模板是一致的。因而当咱们再次打开记事本时,记事本就误认为这是一个 UTF-8 编码的文件,让咱们把第一个字节的 110 和第二个字节的 10 去掉,咱们就获得了“00001 101010”,再把各位对齐,补上前导的 0,就获得了“0000 0000 0110 1010”,很差意思,这是 UNICODE 的 006A,也就是小写的字母"j",而以后的两字节用 UTF-8 解码以后是 0368,这个字符什么也不是。这就是只有“联通”两个字的文件没有办法在记事本里正常显示的缘由。
其实,若是记事本软件经过检测文件头标识来肯定文件的编码方式就可避免该状况,即若是是 UTF-8 文件,则其文件前三个字节应该是 EF BB BF。
注3:地球人都知道,大多数Intel兼容机都采用小端法表示数据,而大多数IBM和Sun Microsystems的机器则采用大端法表示数据。例如 0x1234567 这个数,大端法在内存中按字节依次存放为:01 23 45 67,小端法在内存中按字节依次存放为:67 45 23 01。
不多有鱼油知道他们事实上是来源于Jonathan Swift的《格列佛游记》一书。
如下是Jonathan Swift 在1726 年关于大小端之争历史的描述:
我下面要告诉你的是,Lilliput 和Blefuscu 这两大强国在过去36 个月里一直在苦战。 战争开始是因为如下的缘由:咱们你们都认为,吃鸡蛋前,原始的方法是打破鸡蛋较大的一端, 但是当今皇帝的祖父小时候吃鸡蛋,一次按古法打鸡蛋时碰巧将一个手指弄破了,所以他的父 亲,当时的皇帝,就下了一道敕令,命令全体臣民吃鸡蛋时打破鸡蛋较小的一端,违令者重罚。 老百姓们对这项命令极为反感。历史告诉咱们,由此曾发生过6 次叛乱,其中一个皇帝送了命, 另外一个丢了王位。这些叛乱大多都是由Blefuscu 的国王大臣们煽动起来的。叛乱平息后,流亡 的人老是逃到那个帝国去寻救避难。据估计,前后几回有11 000 人情愿受死也不愿去打破鸡蛋 较小的一端。关于这一争端,曾出版过几百本大部著做,不过大端派的书一直是受禁的,法律也 规定该派的任何人不得作官。(此段译文摘自网上蒋剑锋译的《格列佛游记》第一卷第4 章。)
在他那个时代,Swift 是在讽刺英国(Lilliput)和法国(Blefuscu)之间持续的冲突。Danny Cohen,一位网络协议的早期开创者,第一次使用这两个术语来指代字节顺序[25],后来这个术 语被普遍接纳了。