谈一谈字符编码的事

谈一谈字符编码的事

    字符编码是程序员最头疼的问题,有一句话可以形容字符编码的重要性。“大家都统一用UTF,不然最后怎么死的都不知道”。因为文字是用户日常交流的基础,而字符编码是文字在计算机系统中的表示,如果在一个系统中字符编码都没有确定,那么最后很容易出现乱码问题。对于乱码问题,当涉及到的模块增多,系统间的交互增多,解决起来就变得异常困难。字符编码无小事,不要认为你写的程序很小,不涉及到多个模块间的交互,就可以避免字符编码问题。这个想法是完全的错误。编码问题非常基础,如一个字符串拷贝函数就会涉及,估计没有人会不用吧。那么当你使用字符处理函数时,有考虑过内存里面的0101到底采用是什么编码吗?经过操作的字符串最后还能否会被转化为可读的文字。下面我们谈一谈字符编码的问题。

1.        什么是字符编码

字符编码,指对人们使用的文字中的每一个符号,用二进制01组成的串进行表示。不同的符号对应的01串不同,每个符号对应的01串就是该符号的编码。

2.        字符编码中的常见概念

A.       ASCII

ASCIIAmerican Standard Code for Information Interchange,美国信息互换标准代码,ASCⅡ)是基于拉丁字母的一套电脑编码。它主要用于显示现代英语和其他西欧语言,它采用单字节编码

ASCII共定义了128个字符,其中33个字符无法显示,在33个字符之外的是95个可显示的字符,包含用键盘敲下空白键所产生的空白字符也是1个可显示字符(显示为空白)。可以在这个网站查询到ASCII的对照表http://ascii.911cha.com/

补充:ASCII第一次以规范标准的形态发表是在1967年,最后一次更新则是在1986年,至今为止共定义了128个字符,其中33个字符无法显示(这是以现今操作系统为依据,但在DOS模式下可显示出一些诸如笑脸、扑克牌花式等8-bit符号),且这33个字符多数都是已废的控制字符,控制字符的用途主要是用来操控已经处理过的文字。

B.       GB2312

ASCII码是单字节编码方式,对于汉字有10万多个字符的语言,单字节编码仅能表示256个字符。

GB 2312GB 2312-80是一个简体中文字符集的中国国家标准,全称为《信息交换用汉字编码字符集·基本集》,又称为GB0,由中国国家标准总局发布,198151日实施。GB2312编码通行于中国大陆。

具体的编码规则就不研究了,开发时不用去了解具体的编码规则。我们只要知道每个汉字及符号以两个字节来表示。同时,GB 2312标准共收录6763个汉字,其中一级汉字3755个,二级汉字3008个,可见生僻字不能显示。

补充:GB 2312还收录了包括拉丁字母、希腊字母、日文平假名及片假名字母、俄语西里尔字母在内的682个全角字符。GB 2312的出现,基本满足了汉字的计算机处理需要,它所收录的汉字已经覆盖中国大陆99.75%的使用频率。新加坡等地也采用此编码。

C.        ANSI编码

因为ASCII码的局限性,不同的国家和地区制定了不同的扩展标准,由此产生了 GB2312, BIG5, JIS 等各自的编码标准。在简体中文系统下,ANSI 编码代表 GB2312 编码,在日文操作系统下,ANSI 编码代表 JIS 编码。 不同 ANSI 编码之间互不兼容,当信息在国际间交流时,无法将属于两种语言的文字,存储在同一段 ANSI 编码的文本中。

D.       Unicode编码

Unicode也是一种字符编码方法,不过它是由国际组织设计,可以容纳全世界所有语言文字的编码方案。Unicode的学名是"Universal Multiple-Octet Coded Character Set",简称为UCSUCS可以看作是"Unicode Character Set"的缩写。

UCS有两种格式:UCS-2UCS-4。顾名思义,UCS-2就是用两个字节编码,UCS-4就是用4个字节(实际上只用了31位,最高位必须为0)编码。

UCS规定了怎么用多个字节表示各种文字。怎样存储、传输这些编码,是由UTF(UCS Transformation Format)规范规定的,常见的UTF规范包括UTF-8UTF-7UTF-16

E.        UTF编码

UTF-8就是以8位为单元对UCS进行编码,它是一种变长的编码方式。它可以使用1-4个字节表示一个符号,根据不同的符号而变化字节长度。从UCS-2UTF-8的编码方式如下:

例如“汉”字的Unicode编码是6C496C490800-FFFF之间,所以肯定要用3字节模板了:1110xxxx 10xxxxxx 10xxxxxx。将6C49写成二进制是:0110 110001 001001, 用这个比特流依次代替模板中的x,得到:11100110 10110001 10001001,即E6 B1 89

UTF-1616位为单元对UCS进行编码。对于小于0x10000UCS码,UTF-16编码就等于UCS码对应的16位无符号整数。对于不小于0x10000UCS码,定义了一个算法。不过由于实际使用的UCS2,或者UCS4BMP必然小于0x10000,所以就目前而言,可以认为UTF-16UCS-2基本相同。UCS-2只是一个编码方案,UTF-16却要用于实际的传输,所以就不得不考虑字节序的问题。

F.        字节序(Little endianBig endian)和BOM

UTF-8以字节为编码单元,没有字节序的问题。UTF-16以两个字节为编码单元。

UTF-16使用的两个字节存在一个顺序的问题。比如“奎”的Unicode编码是594E,“乙”的Unicode编码是4E59。那么当我们收到UTF-16字节流“594E”,那么这是“奎”还是“乙”?

字节序就是规定UTF-16所用两个字节的顺序。以汉字”严“为例,Unicode码是4E25,需要用两个字节存储,一个字节是4E,另一个字节是25。存储的时候,4E在前,25在后,就是Big endian方式;25在前,4E在后,就是Little endian方式。

UCS编码中有一个叫做"ZERO WIDTH NO-BREAK SPACE"的字符,它的编码是FEFF。而FEFFUCS中是不存在的字符,所以不应该出现在实际传输中。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来标记文本文件的编码方式的。Windows中的Unicode字符一般指UCS2UTF16-LE编码。

3.        编码间如何转化

A.       ANSI编码和UCS-2 LE编码

我们以汉字“你好”为例进行说明,先看一下“你好”的各种编码:

汉字

ANSI

UTF-8

UCS-2 BE

UCS-2 LE

你好

C4 E3 BA C3

EF BB BF BOM区分编码)

E4 BD A0 E5 A5 BD

FE FF BOM表示字节序)

4F 60 59 7D

FF FE BOM表示字节序)

60 4F 7D 59

 

输出编码程序:

1)   ANSI编码

char* pchar = "你好";

 

int len = strlen(pchar);

for ( int i=0; i<len; i++ )

{

     char each = pchar[i];

     printf("Hex: %x\r\n",each );

}

printf("===========================\r\n");

2)   UCS-2 LE

wchar_t* pwchar = L"你好";

int wlen = wcslen( pwchar );

int bytelen = wlen * sizeof( wchar_t );

char* point = (char*) pwchar;//强制转化为char*,按照一个字节一个字节的方式输出内存中的内容

for ( int i=0; i<bytelen; i++ )

{

     char each = point[i];

     printf("Hex: %x\r\n",each );

}

printf("===========================\r\n");

B.       ANSIUCS-2 LE之间的转换

1)   Ansi转化为Unicode

MultiByteToWideChar返回的是字符数,需要乘以sizeof(wchar_t)转化为内存大小。返回的字符数包含了字符串的结尾字符’\0’需要的一个字符。

所以输出结果:

2)   Unicode转化为Ansi

WideCharToMultiByte返回的是需要的字节数,可以直接分配内存,返回的字节数包含目标ANSI’\0’为结尾的字节数。

所以输出结果:

C.        UTF-8UCS-2 LE之间的转换

1)   Unicode转化为UTF-8

WideCharToMultiByte返回的是需要的字节数,可以直接分配内存,返回的字节数包含目标UTF-8’\0’为结尾的字节数。

所以输出结果:

UTF-8’\0’以单字节表示

4.        不同字符编码下字符串如何结尾?

    字符串都以\0结束,但是不同编码存储\0所用的字节数不同。通过上面的实验可以看到,ANSIUTF-8采用单字节存储\0Unicode采用双字节存储\0

5.        URLENCODE是字符编码吗?

    URLENCODE与前面的ANSIUnicode,编码不同。URLENCODE是对字符经过ANSIUnicode编码后的结果,再次转化便于URL传输。

比如:

“中文” -> GB2312编码为D6D0CEC4,经过Encode -> %D6%D0%CE%C4

“中文”-> UTF-8编码为E4B8ADE69687,经过Encode -> %E4%B8%AD%E6%96%87





原文地址:https://www.cnblogs.com/lqxinxin/p/2855791.html