从程序员的角度看ascii, gb2312, unicode, utf-凯发app官方网站

凯发app官方网站-凯发k8官网下载客户端中心 | | 凯发app官方网站-凯发k8官网下载客户端中心
  • 博客访问: 572336
  • 博文数量: 136
  • 博客积分: 893
  • 博客等级: 中士
  • 技术积分: 1001
  • 用 户 组: 普通用户
  • 注册时间: 2012-03-31 09:18
个人简介

生命可以终止,梦想不会!

文章分类

全部博文(136)

文章存档

2016年(4)

2015年(2)

2014年(5)

2013年(7)

2012年(118)

相关博文
  • ·
  • ·
  • ·
  • ·
  • ·
  • ·
  • ·
  • ·
  • ·
  • ·

分类: linux

2016-08-10 18:11:03

原文地址: 作者:iarm

字符编码是怎么回事 

0. 概念
字节是计算机的最基本存储单位,一个字节包括8个位.
字符是一种文字的基本单位,比如'a' 是一个字符,'汉' 也是一个字符.

1. 计算机被发明之后,程序员们编写了很多复杂的计算让计算机运行.
但是一个问题是,计算机如何把辛苦计算的结果告知程序员? 假设计算机把计算结果放在某个寄存器,内容是 1010010
总不能让程序员去检测每个引脚的电位吧? 还是得有个显示器.
显示器是依靠点阵来显示图像的. cpu必须告诉显示器,当cpu 把一个字节的数据比如 00101010 放入显示器寄存器的时候,显示器要显示怎样的一个点阵(图像),这个图像就是我们人类可以看得懂的字符. 这样问题就解决了,比如当cpu把00110001放入显示器寄存器时,显示器就显示(控制点阵画出一个图像-字体)字符 "1", 这是一个查表的过程,内存中的值(内码)和字符是一一对应的. 问题是这个对应关系是可以自由确定的,我可以指定显示器把 00110001(内码) 显示为字符 "1",也可以指定显示为字符"2". 这样当然会引起混乱,同一个内码被映射为不同的字符,不利于人们的交流. 

美国国家标准学会(ansi)决定着手解决这个问题, 英语有一个很小的字符集,26个字母再加上一些控制字符和标点符号,7位2进制值就足以表示所有的变化.于是ansi公布了一个标准的对应关系,以字节为单位. 当内码为 0110001 时,大家都公认它代表字符 "1",在所有显示器都显示为同一个字符. 这样大家就可以按照同一个标准相互交换数据而不会引起误解. 这个表就是一个包含了128项的对应关系, 叫做 "ascii", 美国信息交换标准代码.


2.对于中国这样不使用abc字符的国家来说,如何显示自己的文字是一个大问题. 我们可以制定一个内码表,指定一个内码对应一个汉字. (由于中文的字符非常多,所以一个字节是不够的,至少也要有2个字节存储一个内码.) 这是很容易的,只要国家公布一个标准的内码字符对应表,大家都遵照这个表就可以了.但是还是有一些问题要注意:

(1). 即使在中国,计算机还是得能显示英文吧? 而英文的内码已经有 ascii 标准在先,并且已经有无数的程序已经在这个标准上运行了很多年,成为不可或缺的部分. 所以我们新制定的内码表必须和 ascii 兼容.


(2) 很多c语言的库函数是以内码0作为字符串结束标志的,为了兼容那些以前就已经编写好,并且运行良好的程序,我们指定的内码中不能含有值为0的字节.

巧合的是,所有的ascii内码的最高位都为0. 那么我们只要让第一个和第二个字节(一个汉字占用2个字节)的最高位都为1,这样既和ascii内码区分开来,又不会出现0.符合这个规则的内码(2字节长)理论上一共可以标识 127 * 127 = 16129个字符(实际上只用了6000多个码位,保留了一些,不过也已经够用了,常用的中文字符只有4000多个).

我们国家公布的这个内码标准表就是gb2312.

原有的英文软件可以很好的运行,c的库函数也不用做修改, 比如 strlen("abc") 在gb2312表示的内码中, 由于gb2312对英文字符的编码是和ascii完全一样的,所以返回3.对于 strlen("a汉字"), 由于strlen()是以内码为0作为边界的,而所有中文字符的gb2312内码高位都为1,不会出现0,并且每个汉字占用2个字节,所以 strlen 返回5. 对于程序来说只要检查一个字节的最高位,就可以很容易的判断这个字符是中文还是英文字符,非常方便.

"一个字母一个字节,一个汉字2个字节" 的观念深入人心.

有了gb2312之后,汉字显示/存储/交换就基本上没什么问题了.
几乎所有的非英语国家都制定了和gb2312类似兼容ascii的内码字符对应表.
(big5 由于有几个字符的内码和ascii相同但表示不同的字符,不符合2.(1)条件.所以被认为是有"瑕疵"的.)

3. 很明显,gb2312的码位是不够的, 一个例子就是有很多人的人名电脑里打不出来.(只有6000多个码位,而<<康熙字典>>就收录了4万多个汉字).所以后来有出现了诸如gbk, gb18030以及同期流行于台湾香港的big5编码. 虽然编码有些不同, 但是设计思想是一致的: 兼容ascii,并确保不会有某个字节值为0的内码出现.有一个共同的特点是: 它们都是局部的标准,只流行于某个地区/国家内.

4.由于内码表都是各个国家独自制定的,同一个内码,在不同的国家表示的可能是不同的字符.(除了ascii字符, ascii字符在所有国家指定的内码表中都有同样的值.)不利于国家间的信息交换. 于是 unicode 应运而生.

unicode 采用一种很简单的办法来解决这个问题. 就是采用2个 - ucs-2 (或者4个字节 - ucs-4)字节标识一个字符. 2个字节总共可以表示65535个字符,足够表示世界上的所有语言的所有字符.(汉字不就有4万多个吗,65535怎么够. 我估计只是常用的汉字几千个被编在ucs-2中吧. 目前被正式编码到unicode码位的只有不超过65534个, 所以就目前的情况来说,用2个字节是可以的.) 注意 ucs-4, ucs-2 和 ascii是向下兼容的,只要前面补0就可以了.这点很重要,可以一直扩展下去包含全宇宙的字符.

现在地球上每个字符在所有采用unicode字符编码的计算机内都有一个唯一的内码了.
要注意, 除了ascii字符外,其他国家文字的字符的内码是重新分配过的,不一定和各国原有的编码相同.比如大部分汉字的gb2312内码和unicode 内码都是不同的.

5. 很显然,对于英语国家来说,unicode内码非常浪费空间,对于ucs-2 浪费了50%的存储空间,对于 ucs-4 则浪费了70%的存储空间. 而且还有一个更大的问题, unicode的内码中含有很多 '\0', 原有的c标准库函数没办法处理这些字符串.于是有人发明了一种针对unicode的变换规则,把unicode字符串中的0去除. 注意这个变换规则不是通过查表实现的,而只要用一些位移操作就可以实现. 这就是utf8. utf8 只是 unicode内码在存储/传输时的状态. 而从gb2312编码转换到unicode编码需要查表. utf8 和 unicode 的关系 与 gb2312 和 unicode的关系有本质的不同. utf8 和 unicode 是一个人的两个面孔, gb2312 和 unicode 是两个人. 所以,要实现utf8编码到gb2312编码的转换必须先把 utf8编码还原为unicode编码,再通过查表的方式,把unicode编码转化为gb2312编码.

以上,虽然说得不是很严谨(比如gb2312其实是区位码,真正的内码还要给每个字节加上a0, 这些我都没提,免得分散注意力),但是文字编码的原理大致就是那么回事,理解就好了. 要想

详细了解细节google一下能找到很多资料.


字符编码的编程相关问题. 

1. windows从nt开始,内核使用unicode内码. 为了向前兼容,前端使用的还是gb2312内码(中文环境).  所以用 visual studio 编写代码时, 如果在cpp文件中写这样一句 const char* psztext = "中文", 编译器让 psztext 指向"中文"的gb2312内码值的内存空间. 当调用 printf(psztext)时, winapi 把这个gb2312字符串转化为unicode字符串再输出.(windows自然知道你的编码是gb2312,因为你在windows系统中设置的语言区域是中国, codepage 936. 如果改成其它语言,就会显示为乱码.)
微软非常鼓励windows程序员用unicode编写程序,很明显,由于windows内核就是原生的unicode环境,调用api时,省却了编码转换的操作,效率更高. 而且一个额外的好处就是不会有乱码. 注意,ms的c/c 编译器把sizeof(wchar_t)设置为2个字节. 由于目前所有的unicode字符只有65534个码位(bmp),所以用2个字节是没问题的.

2. linux系统(比如ubuntu)现在一般都用utf8编码了. 我们在linux下创建cpp文件并添加同样的: const char* psztext = "中文" 编译器会让 psztext 指向"中文"utf8的内码值的内存空间.linux的终端可以理解为一个只接收utf8字符串的显示器. 任何被写到终端的字符流都被认为是是一个utf8字符流.所以,编程的时候,从外部(文件或者控制台)读入utf8字符流,转换为wchar_t,然后程序在内部使用宽字符处理,最后再把要输出的宽字符流转换为utf8字符流并输出到控制台/文件中. 用户程序可以通过环境变量lang的值得知当前的系统环境所使用的字符编码.由此可见,c库函数的 mbstowcs()/wcstombs()主要是为应付这种情况设计的. 如果要处理xml, html 等等有明确指明字符编码的字符流,用专门的字符转换库更为方便.
为什么很多windows下的c源文件的注释在linux编辑器下会显示为乱码就很好理解了.

3. 字符编码转换相关的函数和库

windows 的字符转换函数: multibytetowidechar() / widechartomultibyte()
linux 的字符转换库: glibc iconv函数组.
c标准库使用的 mbstowcs()和wcstombs()和 locale 相关,用起来很不方便,而且功能有限.

(注意不要假设 wchar_t 的大小, 它可能是4字节也可能是2字节,取决于编译器. 比如 ms vc9.0 (2008) 里, sizeof(wchar_t) = 2, 而在gcc中, sizeof(wchar_t) = 4.)

4. 给定一个ansi兼容的字符串(包括gb2312,gbk,utf8等),无法确定它的编码类型,只能猜测.所以不要指望会有一个万能的转换函数.

5. bom (byte order mark)unicode: ff fe / fe ff 和 utf8: ef bb bf 是不完全靠谱的,仅供参考.

最后说明一点,对于不是专门处理字符编码的程序来说,所有字符编码相关的问题只是显示的问题,并不会影响到程序的内在逻辑.
开始用 unicode 来编写我们的代码吧.

=============================================================
 上我自己写的判断(猜测)一个文本是否是utf8文本的函数:

// 返回值说明: 
// 0 -> 输入字符串符合utf-8编码规则,有可能是utf8串
// -1 -> 检测到非法的utf-8编码首字节
// -2 -> 检测到非法的utf-8字节编码的后续字节.

int istextmaybeutf8 (const char* pszsrc)
{
    const unsigned char* puszsrc = (const unsigned char*)pszsrc; // 一定要无符号的,有符号的比较就不正确了.
    // 看看有没有bom表示 ef bb bf
    if( puszsrc[0] != 0 && puszsrc[0] == 0xef && 
        puszsrc[1] != 0 && puszsrc[1] == 0xbb &&
        puszsrc[2] != 0 && puszsrc[2] == 0xbf)
    {
        return 0;
    }

    // 如果没有 bom标识
    bool bisnextbyte = false;
    int nbytes = 0; // 记录一个字符的utf8编码占用几个字节.
    const unsigned char* pcur = (const unsigned char*)pszsrc; 
    
    while( pcur[0] != 0 )
    {
        if(!bisnextbyte)
        {
            bisnextbyte = true;
            if ( (pcur[0] >> 7) == 0) { bisnextbyte = false; nbytes = 1; bisnextbyte = false; } // 最高位为0, ansi 兼容的.
            else if ((pcur[0] >> 5) == 0x06) { nbytes = 2; } // 右移5位后是 110 -> 2字节编码的utf8字符的首字节
            else if ((pcur[0] >> 4) == 0x0e) { nbytes = 3; } // 右移4位后是 1110 -> 3字节编码的utf8字符的首字节
            else if ((pcur[0] >> 3) == 0x1e) { nbytes = 4; } // 右移3位后是 11110 -> 4字节编码的utf8字符的首字节
        %2

阅读(1265) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~
")); function link(t){ var href= $(t).attr('href'); href ="?url=" encodeuricomponent(location.href); $(t).attr('href',href); //setcookie("returnouturl", location.href, 60, "/"); }
网站地图