聊聊字符编码

来源:互联网 发布:计算机科学及编程导论 编辑:程序博客网 时间:2024/05/10 02:50

Question:给定一个二进制串,代表什么含义呢?

二进制串是8个bit为一byte,问题来了
1. 几个byte为一组?
2. 一组代表什么含义呢?

几个概念:

Charset 字符集
  • 字符(Character)是文字与符号的总称,包括文字、图形符号、数学符号等。
  • 一组抽象字符的集合就是字符集(Charset)。
  • 字符集常常和一种具体的语言文字对应起来,该文字中的所有字符或者大部分常用字符就构成了该文字的字符集,比如英文字符集。
Encoding 编码
  • 计算机要处理各种字符,就需要将字符和二进制内码对应起来,这种对应关系就是字符编码(Encoding)。
  • 字符和二进制内码的对应关系就是字符编码。
  • 制定编码首先要确定字符集,并将字符集内的字符排序,然后和二进制数字对应起来。根据字符集内字符的多少,会确定用几个字节来编码。
内码
  • 在计算机科学及相关领域当中,内码指的是“将资讯编码后,透过某种方式储存在特定记忆装置时,装置内部的编码形式”。在不同的系统中,会有不同的内码。
  • 简单理解内码其实就是在内存中处理的编码格式
  • 在以往的英文系统中,内码为ASCII。在繁体中文系统中,目前常用的内码为大五码(Big5)。在简体中文系统中,内码则为国标码(国家标准代码:现在强制要求使用GB18030标准;较旧计算机仍然使用GB2312)。而统一码(Unicode)则为另一常见内码。
  • 为了软件开发方便,如国际化与本地化,现在许多系统会使用Unicode做为内码,常见的操作系统Windows、Mac OS X、Linux皆如此。许多编程语言也采用Unicode为内码,如Java、Python 3。
值得强调的地方
  • 字符集即字符的集合,规定了在这些集合里面有哪些字符,每一个字符都有一个编号(一个整数),但这只是编号不是编码
  • 各个国家和地区在制定编码标准的时候,“字符的集合”和“编码”一般都是同时制定的。
  • 每个字符集合都至少有一种编码方式,所以字符集也叫做被编码过的字符集(Coded Character Set),所以经常用字符集指代字符集用的编码方式。平常我们所说的“字符集”,比如:GB2312, GBK, JIS 等,除了有“字符的集合”这层含义外,同时也包含了“编码”的含义。
  • 反之,每种编码都限定了一个明确的字符集合,因此会有 “UTF-8 字符集”这种说法。
对上述的一点重复说明
  • 最初,一种字符集对应字符编码,反过来也是一样的,所以例如ASCII既可以说是字符集,也是字符编码
  • 然而,在Unicode出现之后,这种情况发生了改变,Unicode字符集由于实际情况出现了编码方式与实现方式不统一,所以Unicode可以单纯地当成是字符集,而不能和前面一样,同时也是字符编码。
  • 最后,要强调编号的问题。对于任意一种字符集来说,一定要知道字符的位置,这就是编号,但能不能是编码呢?
    1. 举例:对于一种给定的字符集,例如ASCII码,编号(0-127)可以是编码;然而其他一些字符集来说,其位置并不是单纯地从0或1开始顺序排列,而是有很多其他的考虑,如兼容ASCII,使用区位码等等;而如果认为其实际使用的编码方式就是本来编号的方式的话,两者也可以是统一的。
    2. 因为Unicode的出现,要额外的区分编号与编码方式,因为编码方式多样化。因此除了Unicode不去额外的区分问题也不大

要看更为详细的相关概念请查阅wikipedia。


ASCII

上个世纪60年代,美国制定了一套字符编码,对英语字符与二进制位之间的关系,做了统一规定。这被称为ASCII码,一直沿用至今。
ASCII码一共规定了128个字符的编码,比如空格”SPACE”是32(二进制00100000),大写的字母A是65(二进制01000001)。这128个符号(包括32个不能打印出来的控制符号),只占用了一个字节的后面7位,最前面的1位统一规定为0。

问题:其他国家有自己的文字,怎么办?


其他

西欧字符编码简介

ISO 8859,全称ISO/IEC 8859,是国际标准化组织(ISO)及国际电工委员会(IEC)联合制定的一系列8位字符集的标准,现时定义了15个字符集。
ASCII收录了空格及94个“可印刷字符”,足以给英语使用。
但是,其他使用拉丁字母的语言(主要是欧洲国家的语言),都有一定数量的变音字母,故可以使用ASCII及控制字符以外的区域来储存及表示。
除了使用拉丁字母的语言外,使用西里尔字母的东欧语言、希腊语、泰语、现代阿拉伯语、希伯来语等,都可以使用这个形式来储存及表示。
在字符编码上,为了实现欧洲,如此多种的地区和语系,支持其所用的字符,然后发展处一个国际标准,叫做ISO/IEC 8859,简称ISO8859 (8位单字节编码)

  • ISO8859,其下分了很多分支,从1到15,分别叫做ISO8859-1,ISO8859-2,……,ISO8859-15
  • 可以说:ISO8859,包含了整个欧洲所用的各种字符;
  • 其中,最最常用的,就是:ISO8859-1,又常被称为Latin-1,表示的就是西欧字符

ANSI

单字节的ASCII已无法满足需求,每个语言就制定了一套自己的编码,由于单字节能表示的字符太少,而且同时也需要与ASCII编码保持兼容,所以这些编码纷纷使用了多字节来表示字符,他们的规则是,如果第一个字节是\x80以下,则仍然表示ASCII字符;而如果是\x80以上,则跟下一个字节一起(共两个字节)表示一个字符,然后跳过下一个字节,继续往下判断。

这里,IBM发明了一个叫Code Page的概念,将这些编码都收入囊中并分配页码,GBK是第936页,也就是CP936。所以,也可以使用CP936表示GBK。

MBCS(Multi-Byte Character Set)是这些编码的统称。目前为止大家都是用了双字节,所以有时候也叫做DBCS(Double-Byte Character Set)。必须明确的是,MBCS并不是某一种特定的编码,Windows里根据你设定的区域不同,MBCS指代不同的编码,而Linux里无法使用MBCS作为编码。微软用了ANSI 来代替了MBCS。同时,在简体中文Windows默认的区域设定里,指代GBK。即使知道是ANSI,我们还需要知道这是哪国文字才能解码,因为这些编码都互相冲突。另外,你无法用一段ANSI 编码表示既有汉字、又有韩字的文本。

简体中文码 GB

GB编码早期收录的汉字不足一万个,基本满足日常使用需求,但不包含一些生僻的字,后来在一个个新版本中加进去。每扩展一次都完全保留之前版本的编码,所以每个新版本都向下兼容。发展:GB2312(对应微软CP936)-> GB13000 -> GBK(CP936) -> GB18030(对应微软CP54936)

  • 编码的兼容性:GB2312 < GBK < GB18030
  • 包含的字符个数:GB13000和GBK基本上是一样的(但是编码不兼容)
  • 中文编码和微软的代码页的关系是:
    • GB2312==旧的CP936
    • GBK==新的CP936
    • GB18030==CP54936

补充全角和半角的知识:在这些编码里,我们还把数学符号、罗马希腊的字母、日文的假名们都编进去了,连在 ASCII 里本来就有的数字、标点、字母都统统重新编了两个字节长的编码,这就是常说的”全角”字符,而原来在127号以下的那些就叫”半角”字符了。

繁体中文码

对于台湾等地区,都是用的是繁体中文,其和我们的编码不同
繁体中文一般都是用的是BIG5,中文一般称为大五码,其对应者微软的CP950


Unicode 和 UCS

  • Unicode(中文有多个译名:万国码、国际码、统一码、单一码)是一种字符编码标准(字符集),由统一码联盟负责拟定。
  • Universal Character Set(UCS)(中文译名:通用字符集、通用多八位编码字符集、广用多八比特编码字元集)是 ISO 10646 标准定义的字符编码标准(字符集)。
  • Unicode 自 2.0 版本以后,与 UCS Level 3 兼容。本文中一律以 Unicode 称之。

Unicode 知识补充

  • 在表示一个Unicode的字符时,通常会用“U+”然后紧接着一组十六进制的数字来表示这一个字符。在基本多文种平面(英文:Basic Multilingual Plane,简写BMP。又称为“0号平面”、plane 0)里的所有字元,要用四位十六进制数(例如 U+4AE0,共支持六万多个字符);在0号平面以外的字符则需要使用五位或六位十六进制数。
  • 当前的 Unicode 版本为7.0,2014年6月16日发布,收录了超过10万个字符。Unicode 将6字节长度的编码空间划分为17个平面(Plane),每个平面有65536个代码点,目前只使用了少数平面。其中0号平面(U+0000 ~ U+FFFF)被称作基本多文种平面(Basic Multilingual Plane,BMP),用于存放现有的主要文字、符号,编码长度为4字节,其中前256个字符(U+0000 ~ U+00FF)与 ISO 8859-1 兼容。

Unicode的问题

问题:

  1. 如何才能区别Unicode和ASCII?计算机怎么知道三个字节表示一个符号,而不是分别表示三个符号呢?
  2. 英文字母只用一个字节表示就够了,如果Unicode统一规定,每个符号用三个或四个字节表示,那么每个英文字母前都必然有二到三个字节是0,这对于存储来说是极大的浪费,文本文件的大小会因此大出二三倍,这是无法接受的。

它们造成的结果是:

  1. 出现了Unicode的多种存储方式(编码方式),也就是说有许多种不同的二进制格式,可以用来表示Unicode。
  2. Unicode在很长一段时间内无法推广,直到互联网的出现。

Unicode

互联网的普及,强烈要求出现一种统一的编码方式。

Unicode的编码和实现方式

大概来说,Unicode编码系统可分为编码方式和实现方式两个层次。

  1. 编码方式
    统一码的编码方式与ISO 10646的通用字符集概念相对应。目前实际应用的统一码版本对应于UCS-2,使用16位的编码空间。也就是每个字符占用2个字节。这样理论上一共最多可以表示216(即65536)个字符。基本满足各种语言的使用。实际上当前版本的统一码并未完全使用这16位编码,而是保留了大量空间以作为特殊使用或将来扩展。
  2. 实现方式
    Unicode的实现方式不同于编码方式。一个字符的Unicode编码是确定的。但是在实际传输过程中,由于不同系统平台的设计不一定一致,以及出于节省空间的目的,对Unicode编码的实现方式有所不同。Unicode的实现方式称为Unicode转换格式(Unicode Transformation Format,简称为UTF)

值得注意的是:
在wikipedia上Unicode编码方式为UCS-2(或更复杂为UCS-4),实现方式为UTF;所以一定要把Unicode也作为编码方式,一般就指的是UCS-2(UCS-4)
然而UCS-2基本上就是UTF-16的子集,UCS-4基本上就是UTF-32;所以在除了utf-8的情况下,编码方式和实现方式(此外还有储存方式,传输方式)等词很多时候都是同一个意思,分得并不清楚

  • UTF-16 需要1个或者2个16位长的码元来表示,因此这是一个变长表示。
    UTF-16可看成是UCS-2的父集。在没有辅助平面字符(surrogate code points)前,UTF-16与UCS-2所指的是同一的意思。但当引入辅助平面字符后,就称为UTF-16了。现在若有软件声称自己支持UCS-2编码,那其实是暗指它不能支持在UTF-16中超过2bytes的字集。对于小于0x10000的UCS码,UTF-16编码就等于UCS码。
  • UTF-32 (或 UCS-4) : 对每一个Unicode码位使用恰好32比特
    因为UTF-32对每个字符都使用4字节,就空间而言,是非常没有效率的。特别地,非基本多文种平面的字符在大部分文件中通常很罕见,以致于它们通常被认为不存在占用空间大小的讨论,使得UTF-32通常会是其它编码的二到四倍。
  • 以上两种不兼容ASCII码,也是一个较大的问题。

UTF-8编码简介

UTF-8(8-bit Unicode Transformation Format)是一种针对Unicode的可变长度字符编码,也是一种前缀码。它可以用来表示Unicode标准中的任何字符,且其编码中的第一个字节仍与ASCII兼容,这使得原来处理ASCII字符的软件无须或只须做少部分修改,即可继续使用。因此,它逐渐成为电子邮件、网页及其他存储或发送文字的应用中优先采用的编码。

UTF-8使用一至六个字节为每个字符编码(尽管如此,2003年11月UTF-8被RFC 3629重新规范,只能使用原来Unicode定义的区域,U+0000到U+10FFFF,也就是说最多四个字节)

UTF-8的编码规则很简单,只有二条:

  1. 对于单字节的符号,字节的第一位设为0,后面7位为这个符号的unicode码。因此对于英语字母,UTF-8编码和ASCII码是相同的。
  2. 对于n字节的符号(n>1),第一个字节的前n位都设为1,第n+1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的unicode码。
Unicode符号范围(十六进制) UTF-8编码方式(2进制) 0000 0000 ~ 0000 007F 0xxxxxxx 0000 0080 ~ 0000 07FF 110xxxxx 10xxxxxx 0000 0800 ~ 0000 FFFF 1110xxxx 10xxxxxx 10xxxxxx 0001 0000 ~ 0010 FFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

例如: “严”的unicode是4E25(100111000100101)
根据上表,可以发现4E25处在第三行的范围内(0000 0800 ~ 0000 FFFF),因此”严”的UTF-8编码需要三个字节,即格式是”1110xxxx 10xxxxxx 10xxxxxx”。然后,从”严”的最后一个二进制位开始,依次从后向前填入格式中的x,多出的位补0。这样就得到了,”严”的UTF-8编码是”11100100 10111000 10100101”,转换成十六进制就是E4B8A5。


Question: 如何知道一个文本的字节顺序,编码方式

字节顺序

易于看出对于utf-8来说不存在这个问题,但是对于其他的多字节编码方式例如gbk,utf-16均需要考虑

Little endian和Big endian

  • Unicode码可以采用UCS-2格式直接存储。第一个字节在前,就是”大头方式”(Big endian),第二个字节在前就是”小头方式”(Little endian)。
    以汉字”严”为例,Unicode码是4E25,需要用两个字节存储,一个字节是4E,另一个字节是25。存储的时候,4E在前,25在后,就是Big endian方式;25在前,4E在后,就是Little endian方式。
  • 这两个古怪的名称来自英国作家斯威夫特的《格列佛游记》。在该书中,小人国里爆发了内战,战争起因是人们争论,吃鸡蛋时究竟是从大头(Big-Endian)敲开还是从小头(Little-Endian)敲开。为了这件事情,前后爆发了六次战争,一个皇帝送了命,另一个皇帝丢了王位。

Microsoft Windows操作系统内核对Unicode的支持
Windows操作系统内核中的字符表示为UTF-16小尾序,可以正确处理、显示以4字节存储的字符。但是Windows API实际上仅能正确处理UCS-2字符,即仅以2字节存储的,码位小于U+FFFF的Unicode字符。其根源是Microsoft C++语言把wchar_t数据类型定义为16比特的unsigned short,这就与一个wchar_t型变量对应一个宽字符,可以存储一个Unicode字符的规定相矛盾。相反,Linux平台的GCC编译器规定一个wchar_t是4字节长度,可以存储一个UTF-32字符,宁可浪费了很大的存储空间。
目前在PC机上的Windows系统和Linux系统对于UTF-16编码默认使用UTF-16 LE。

计算机怎么知道某一个文件到底采用哪一种方式编码?

BOM(Byte Order Mark)

  • Unicode规范中定义,每一个文件的最前面分别加入一个表示编码顺序的字符,ZERO WIDTH NO-BREAK SPACE,而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编码了。
开头字节 Charset/encoding EF BB BF UTF-8 FF FE UTF-16/UCS-2, little endian(UTF-16LE) FE FF UTF-16/UCS-2, big endian(UTF-16BE) FF FE 00 00 UTF-32/UCS-4, little endian 00 00 FE FF UTF-32/UCS-4, big-endia 84 31 95 33 GB-18030

BOM的吐槽

  • 不含 BOM 的 UTF-8 才是标准形式,在 UTF-8 文件中放置 BOM 主要是微软的习惯(顺便提一下:把带有 BOM 的小端序 UTF-16 称作「Unicode」而又不详细说明,这也是微软的习惯)。

  • BOM(byte order mark)是为 UTF-16 和 UTF-32 准备的,用于标记字节序(byte order)。微软在 UTF-8 中使用 BOM 是因为这样可以把 UTF-8 和 ASCII 等编码明确区分开。但它和很多协议、规范不兼容。

    • JDK1.5以及之前的Reader都不能处理带有BOM的UTF-8编码的文件,解析这种格式的xml文件时,会抛出异常:Content is not allowed in prolog。
    • Linux/UNIX 并没有使用 BOM,因为它会破坏现有的 ASCII 文件的语法约定
  • UTF-8和带 BOM 的 UTF-8的区别就是有没有BOM。

XML解析读取XML文档时,W3C定义了3条规则:

  1. 如果文档中有BOM,就定义了文件编码;
  2. 如果文档中没有BOM,就查看XML声明中的编码属性;
  3. 如果上述两者都没有,就假定XML文档采用UTF-8编码。

记事本

Windows存文件可以用四种方式,可以在保存的时候看看下面的选项。微软的 Unicode,确实是 UTF-16LE。 使用Windows自带的记事本将文件保存为UTF-8编码的时候,记事本会自动在文件开头插入BOM(虽然BOM对UTF-8来说并不是必须的)。而其它很多编辑器用不用BOM是可以选择的。UTF-8、UTF-16都是如此。

如何得知编码方式及正确存储文件

  1. 不同的编码方式导致了乱码,而在没有BOM的情况下,得知一个文件编码方式只能靠猜了。
  2. BOM又存在诸多问题
  3. 很多软件默认使用utf-8打开文件
  4. 因此,建议不要使用记事本,文件存为 UTF-8 without BOM

进一步的参考资料

值得强调的是,虽然网上的字符编码资料很多,但质量参差不齐,使用概念较为混乱,很多都有或多或少的错误,因此首选请查维基百科
- wikipedia 字符编码
- wikipedia Unicode
- wikipedia 字节序
- wikipedia UTF-8
- wikipedia UTF-16
- wikipedia UTF-32

其他参考资料
- 详细的字符编码资料
- 较为详细的各种字符集、编码解释
- 更多关于的大小端模式东西

0 0
原创粉丝点击