字符编码总结

2014/10/06

本文原创作者:Cloud Chou. 欢迎转载,请注明出处和本文链接

理解字符编码的关键在于区分以下概念:

字符集,编码方式,实现方式(也可以说是转换格式)

术语介绍

  • 1)Charset 字符集

    字符(Character)是文字与符号的总称,包括文字、图形符号、数学符号等。

    一组抽象字符的集合就是字符集(Charset)。

    字符集常常和一种具体的语言文字对应起来,该文字中的所有字符或者大部分常用字符就构成了该文字的字符集,比如英文字符集。

    一组有共同特征的字符也可以组成字符集,比如繁体汉字字符集、日文汉字字符集。

    字符集的子集也是字符集。

    字符集即字符的集合,规定了在这些集合里面有哪些字符,每一个字符都有一个编号(一个整数),但这只是编号不是编码

  • 2)Encoding 编码

    字符和二进制内码的对应关系就是字符编码。

    制定编码首先要确定字符集,并将字符集内的字符排序,然后和二进制数字对应起来。根据字符集内字符的多少,会确定用几个字节来编码。

    每个字符集合都至少有一种编码方式,所以字符集也叫做被编码过的字符集(Coded Character Set),所以经常用字符集指代字符集用的编码方式。

  • 3)ASCII

    ASCII是一种英文字符集,同时也代表一种编码。

    American Standard Code for Information Interchange。

  • 4)ISO 8859

    ISO 8859,全称ISO/IEC 8859,是国际标准化组织(ISO)及国际电工委员会(IEC)联合制定的一系列8位字符集的标准,现时定义了15个字符集。

  • 5)UCS

    Universal Character Set, 通用字符集。

    UCS包含了已知语言的所有字符,UCS还包括大量的图形、印刷、数学、科学符号。

    UCS由ISO制定的ISO 10646(或称ISO/IEC 10646)标准所定义.

    对应两种编码:

    UCS-2: 与unicode的2byte编码基本一样

    UCS-4: 4byte编码,目前是在UCS-2前加上2个全零的byte。

  • 6)Unicode

    Unicode是由unicode公司制定的通用字符集,也包含了世界上所有常用文字。

    Unicode 2.0 与UCS 采用了相同的字库和字码。

    Unicode也可以说是一种编码过的字符集,采用的编码方式也称为Unicode。

    Unicode 采用 2个字节编码,对应于UCS-2。

  • 7)UTF

    Unicode的实现方式称为Unicode转换格式(Unicode Translation Format,简称为 UTF)。

    一个字符的Unicode编码是确定的,但是在实际传输过程中,由于不同系统平台的设计不一定一致,以及出于节省空间的目的,对Unicode的实现方式有所不同。

    Unicode的两种实现方式:

    UTF-8: 8bit长度编码,对于大多数常用字符集(ASCII中0~127字符)它只使用单字节,而对其它常用字符(特别是朝鲜和汉语会意文字),它使用3字节。与字节序无关。

    UTF-16: 16bit长度编码,大致相当于20位编码,值在0至0x10FFFF之间,基本上就是unicode编码的实现,与CPU字节序有关。

  • 8)GB2312

    GB2312是一种简体字集,包括国标简体汉字6763个

  • 9)BIG5

    BIG5字集是台湾繁体字集,包括国标繁体汉字13053个

  • 10)GBK

    GBK字集是简繁字集,包括了GB字集,BIG5字集和一些符号,共包括21003个字符

  • 11)GB18030

    GB18030是国家制定的一个强制性大字集标准,全称为GB18030-2000,它的推出使汉字集有了一个“大统一”的标准

  • 12)ANSI

    ANSI是一种编码方式,使用2个字节来代表一个字符,只有表示ascii字符集的字节最高位是0,表示其它字符的字节最高位是1.

    在简体中文系统下,ANSI编码代表GB2312编码(GB2312其实是一种字符集,也可以说它是一种编码方式),在日文操作系统下,ANSI编码编码代表JIS编码。

  • 13)字节序

    UTF-8以字节为编码单元,没有字节序的问题。UTF-16以两个字节为编码单元,这样就需要弄清楚这个编码单元的字节谁在前谁在后的问题,也就是说要弄清楚编码单元内部的字节序。

    UTF16和Unicode编码等价。

    UCS编码(可以看作Unicode编码)中有一个叫做ZERO WIDTH NO-BREAK SPACE的字符,它的编码是FEFF,如果调换两个字节的顺序,则是FFFE,而Unicode编码里没有字符和FFFE对应。

    所以如果文件开头是FFFE,则表明该文件是小端模式,如果文件开头是FEFF,则表明是大端模式。(这块不理解的话可以看大端模式和小端模式总结)

    字符ZERO WIDTH NO-BREAK SPACE又被称为BOM(Byte Order Mark)。Unicode文本文件(或者说是UTF16文本文件)就是使用该字符来标记编码方式是大端还是小端的。

    UCS规范(可以看作Unicode规范)建议我们在传输字节流前,先传输字符ZERO WIDTH NO-BREAK SPACE。

    这样如果接收者收到FEFF,就表明发送者发送的字节流是大端的,如果接收者收到的编码是FFFE,就表明发送者发送的字节流是小端的。

  • 14) BOM

    Byte Order Mark。即字节序里提到的Unicode字符ZERO WIDTH NO-BREAK SPACE,对应unicode编码是FEFF,如果用UTF-16大端编码就是FEFF,如果用UTF-16小端编码就是FFFE,如果用UTF-8编码就是三字节EFBBBF。

总结:

字符集就是字符的集合,编码是指将字符和二进制数字的对应关系,通常我们提到某个字符集,也会用这个字符集的名称暗指它的默认编码方式,比如提到GB2312,它是一种简体字符集,有时候也会用GB2312指代GB2312字符集的编码方式。

字符集的种类有:ASCII,ISO8859,UCS,Unicode,GB2312,GBK,BIG5,GB18030,UCS和Unicode都是通用字符集,可以表示所有国家的文字和符号,Unicode 2.0和UCS等价。

用字符集代指编码方式的有:ASCII,Unicode,GB2312,GBK,BIG5。

ANSI编码比较特殊,在简体中文系统下,指的是GB2312编码,在日文操作系统下,指的是JIS编码。

Unicode编码用两个字节表示一个字符,实现方式有两种UTF-8和UTF-16:

UTF-8以单字节为编码单元,表示汉字需要3个字节,表示希腊字母用两个字节,无字节序问题

UTF-16以双字节为编码单元,表示任何字符都是2个字节,存在字节序问题,需要用BOM标志字节序。

Unicode编码和UTF-16编码是完全等价的。

UTF8编码示例

UTF8是unicode的一种实现方式,它是一种变长字节的编码方式。

UTF8表示ASCII字符用一个字节,表示希腊字母用两个字节,表示汉字用3个字节。

对于某个字符的UTF-8编码,如果只有一个字节则其最高二进制位为0,如果是多字节,其第一个字节从最高位开始,连续的二进制位值为1的个数决定了其编码的位数(也就是说用到几个字节表示一个字符),其余各字节均以10开头。UTF-8最多可用到6个字节。

如表:

1字节 0xxxxxxx

2字节 110xxxxx 10xxxxxx

3字节 1110xxxx 10xxxxxx 10xxxxxx

4字节 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

5字节 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx

6字节 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx

因此UTF-8中可以用来表示字符编码的实际位数最多有31位,即上表中x所表示的位。除去那些控制位(每字节开头的10等),这些x表示的位与UNICODE编码是一一对应的,位高低顺序也相同。

实际将UNICODE转换为UTF-8编码时应先去除高位0(Unicode编码有两个字节组成,表示ASCII字符时,第一个字节为全0,第二个字节和ASCII字符编码完全一样),然后根据所剩编码的位数决定所需最小的UTF-8编码位数。

因此那些基本ASCII字符集中的字符(UNICODE兼容ASCII)只需要一个字节的UTF-8编码(7个二进制位)便可以表示。

也正是因为UTF8以字节为单位进行编码,若最高位是0则表示该字节表示的是一个ASCII字符,若某个字节的最高两位是10,则该字节应该和前面的字节一起编码表示某个字符,若是11,则该字节应和后面多个字节一起编码表示某个字符,所以UTF-8编码不存在字节序的问题。

Unicode编码中BOM字符的编码是0xFEFF,

二进制对应: 11111110 11111111

需要16位,2字节的utf-8共有11位可用来编码,3字节的utf-8共16位可用来编码,故此需要3字节编码:

1110xxxx 10xxxxxx 10xxxxxx

将BOM的二进制拆分成能填进上述编码的位:

1111 111011 111111

然后再拼接得到的二进制位如下所示:

11101111 10111011 10111111

用十六进制表示就是:

0xEFBBBF

故此BOM在UTF8编码里是0xEFBBBF

所有汉字的unicode码都是两字节,16位,用utf8表示至少需要3字节,故此所有汉字的utf8编码都是3字节。

C++如何处理字符编码

C,C++,Python2 内部字符串使用当前系统默认编码,一般情况下当前系统默认编码是GB2312,即简体中文编码。

C,C++表示字符的类型有char和wchar,char占一个字节,wchar占两个字节,所有编码方式里只有Unicode编码(即UTF-16大端模式和UTF-16小端模式)的编码单元为两字节,故此Unicode编码使用wchar作为字符串的基本单元,对应的字符串类型为wstring。

其它编码方式都使用字节作为基本编码单元,故此都用char作为字符串的基本单元,对应的字符串类型为string。

有两个函数可以用来在wchar类型字符串和char类型字符串之间进行转换:

MultiByteToWideChar和WideCharToMultiByte。

1
2
3
int MultiByteToWideChar(  UINT CodePage,   DWORD dwFlags,         
  LPCSTR lpMultiByteStr,   int cbMultiByte,         LPWSTR lpWideCharStr,
 int cchWideChar        );>

codepage取值:

CP_ACP: 当前系统是Windows ANSI code page。

CP_MACCP: 当前系统是 mac code page

CP_UTF8: UTF8编码方式

1
2
3
int WideCharToMultiByte(  UINT CodePage,   DWORD dwFlags, 
  LPCWSTR lpWideCharStr,  int cchWideChar,   LPSTR lpMultiByteStr, 
  int cbMultiByte,  LPCSTR lpDefaultChar,      LPBOOL lpUsedDefaultChar);

同事的一些开发经验:

C,C++内部字符串默认使用GB2312编码,使用GB2312编码的话调试时可识别汉字,故此从外部文件读入字符串之后一般都先转化成GB2312编码,这样方便调试。

但是将字符串写到文件时通常会选择unicode编码,

发送到网络时会选择UTF-8编码。(可以避免Unicode字节序的问题)

Java如何处理字符编码

Java用String类型表示字符串,String由char类型组成,char类型占两个字节,一个char类型变量就可以表示任意字符,甚至汉字。所以下面代码是合法的:

1
char b='你'

Java内部字符串用Unicode保存,所以调试时可以看到b变量对应的内存如下所示:

unicode_ni

可以知道汉字'你'的unicode编码是4F60,记住unicode编码和utf16大端模式编码是一致的,故此如果将文件存成utf16 big endian格式,可以看到汉字'你'的编码如下所示:

ue_unicode

前面的FEFF就是UTF16编码的BOM,4F60便是汉字'你'。

Python3内部字符串也是用Unicode表示。

常见问题解疑

1)如何选择编码方式?

应当选择兼容性最好的编码方式:utf-8。

GB2312,GBK,Big 5等编码方式互不兼容,不能表示全世界所有语言,unicode编码(即UTF-16编码)和ASCII编码不兼容,故此应该选择UTF-8。

参考材料:

深入了解字符集和编码问题

http://webcenter.hit.edu.cn/articles/2009/04-01/04193356.htm

¥打赏5毛

取消

感谢您的支持,我会继续努力的!

扫码支持
赏个5毛,支持我把

打开支付宝扫一扫,即可进行扫码打赏哦

本篇目录