字符编码

ASCII, ANSI, Unicode, GBK, GB2312, UTF-8; 437代码页,936代码页,,, 这都是些什么?乱糟糟的。。。

网上海量的说明文章,按我自己的理解,总结一下:

问题产生背景:(基础知识,可跳过)
View Code
    1. 计算机如何存储 英文字母标点符号这一系列字符?
      最简单的思想就是给这一大堆字符规定个顺序,然后用自然数编号(注意:最小的自然数是0),
        只让计算机只存储这些字符的数字编号;至于如何在屏幕上呈现给人类的问题交给显示器去做;
    2. 各种字符编码集:
        我们知道计算机的最小存储单元是1字节共8位,1个存储单元的空间最多可以表示2^8=256种信息状态;
      计算机诞生于美国,美国人最开始定义了属于它们自己国家的一套编号"规定" 称为ASCII
    (American Standard Code for Information Interchange,美国信息互换标准代码)
        其中包含了美国人最常用的128个字符(编号从0~127),像美元货币字符$, 英文标点符号,数字,
    大小写的英文字母等;
        
        ASCII太倾向于美国人,英国人发现它们的货币字符 £ 都没有在这一套编号里,
        好在ASCII只占了1个存储单元一半的空间,于是在英国和欧洲不同的国家,为了和ASCII兼容,
        它们使用剩下的128~255个数字,表示属于它们民族专有的字符;称为扩展ASCII字符;

        ASCII太倾向于美国人,扩展ASCII太倾向于非亚洲人;对于中文语言来说,256个数字无法编号所有
        汉字,唯一的办法就是扩大存储空间,于是在中国和亚洲不同的国家,它们使用了多个存储单位来编号
       自己民族的专有字符;这样诞生了很多属于某个特定国家的“编号规定”,像中国的GB码,
    韩国euc-kr编码和日本的Shift_JIS编码等;

        这些多字节编码规则只是从本国语言需求而考虑的,一种存储数据在不同的国家,会被计算机识别
        为各种信息,这给数据共享带来很大麻烦;

     3. 数据共享
        为了解决"本地特色"的各种不同编码给数据共享带来的麻烦,Unicode编码诞生了,它在定义编号的时候
        不是从某一个国家的民族的需求出发,而是从全人类出发,不分国界,把所有国家,所有民族的字符
        都规定一个唯一自然数编号;

 各种字符编码集基本概念梳理

View Code
先说和ASCII兼容的问题:
  美国的文档资料很丰富,很重要,如何和他们的ASCII兼容?
  扩展ASCII字符集是单字节编码,在特定的国家它们很容易实现和ASCII的兼容,方法就是在1字节的高位做个标记
  如果高位为0表示美国人的ASCII字符,高位为1表示本国的特有字符;

对于多字符编码的字符集,在本国该如何实现和ASCII的兼容:
  简单的方法是用2个字节,同扩展ASCII字符的做法,每个字节用ASCII字符后剩下的一半的编号,
  这样可以表示128*128=16384个字符;在识别的时候,如果发现某一字节的高位是1,则它是一个中文字符的一半,
      需要和另一半组合拼成一个完整的汉字;这样避免了识别时的歧义性;我称它为1类扩展ASCII码拼接法

如果超过16384个字符,那怎么办? 
    我们定义第1字节为扩展ASCII码字符,第2字节编号范围在0~255;这样可以表示128*256 = 32768个字符;
    在让计算机识别的时候,从数据的最开始存储单元开始向后扫描,一旦发现有某个字节是扩展ASCII字符,
    那么不管它的下一个字符是扩展ASCII码还是ASCII,都作为中文字符的另一半参与拼接;
    然后,扫描工作从它的"下下"一个字符继续, 对于ASCII字符不需要做拼接处理;我称它为2类扩展ASCII拼接法;

这两种拼接的方法很简单,都可兼容ASCII;但是都没被采用,原因可能是他们太浪费空间了;

看看GB2312字符集(GB2312是信息交换用汉字编码字符集的标准号省去发布年份1980)的做法:
    它占用了94*94 = 8836 个编号,总共收录了包括7455个字符(简体汉字6763个)
    8836个编号划分了94区,每个区包含94个编号(称为该区的位号)
    分区的思想也许是为了统一管理和方便查询吧,比如中文标点符号放1个区,中文数字序号放在同2区;
    然后用第1字节(低字节)表示区号,第2字节(高字节)表示位号;存储的时候为了和ASCII兼容,每个编号+160;
    这种存储方法叫EUC(Extended Unix Code)存储;
    比如‘啊’字位于16区的1号位,存储的时,编号是:176,161; 转换为16进制表示就是B0,A1; (注:这里A1是高字节)
    详细信息请参阅中华人民共和国 国家标准《信息交换用汉字编码字符集--基本集》
    
GB2312字符集
    它通行于大陆,新加波;虽然收录了一些日文,俄文字符,但没有收录少数民族比如藏族语言中的字符;
    还有很多古文和生僻字也没收录;这是它的缺陷;

BIG-5字符集
    它是通行于港台的繁体字符集;共收录13060个字符从A140开始;高字节编码范围是0x81-0xFE,
    低字节编码范围是0x40-0x7E和0xA1-0xFE; 0x8140-0xA0FE是保留区域,用于用户造字区。

GBK 是一个局部通用的字符集,该规范收录了ISO 10646.1中的全部 CJK 字符集,并有所补充;
    (CJK Unified Ideographs中日韩统一表意文字它  是《GB2312-80》、《BIG5》等字符集的超集)
    GBK的整体编码范围是为0x8140-0xFEFE,不包括低字节是0x7F的组合。
    高字节范围是0X81-0xFE,低字节范围是0x40-0x7E和0x80-0xFE。 

    GBK和GB2312都是双字节等宽编码,如果算上和ASCII兼容所支持的单字节,可以理解为是单字节和双字节混合的变长编码

GB18030 字符集收录了所有Unicode3.1中的字符,包括中国少数民族字符,GBK不支持的韩文字符等等,也可以说是世界
    大多民族的文字符号都被收录在内。GB18030编码是变长编码,有单字节、双字节和四字节三种方式。   
    单字节编码范围是0x00-0x7F,完全等同ASCII;
    双字节编码的范围和GBK相同,高字节是0x81-0xFE,低字节的编码范围是0x40-0x7E和0x80-FE;
    四字节编码中第一、三字节的编码范围是0x81-0xFE,二、四字节是0x30-0x39。   


ANSI 编码:
    它是不同系统下各种编码的称谓:没有新增加编码字符集;百度百科上的定义:
    不同的国家和地区制定了不同的标准,由此产生了 GB2312, BIG5, JIS 等各自的编码标准。
    这些使用 2 个字节来代表一个字符的各种汉字延伸编码方式,称为 ANSI 编码。
    比如:在简体中文系统下,ANSI 编码代表 GB2312 编码,在日文操作系统下,ANSI 编码代表 JIS 编码;
    对于ANSI编码而言,0x00~0x7F之间的字符,依旧是1个字节代表1个字符。

代码页:
    代码页是字符集编码的别名;不过是给字符集编码编了个号;
    代码页分单字节代码页和多字节代码页;扩展ASCII字符在单字节代码页下都是可见的;
  437代码页表示美国英语MS-DOS编码
    现在936代码页是GBK编码的别名,但又很细微的差别;(以前936代码页是gb2312)
    根据微软资料,GBK是对GB2312-80的扩展,也就是CP936字码表 (Code Page 936)的扩展
    (之前CP936和GB 2312-80一模一样),
    GBK自身并非国家标准,只是曾由国家技术监督局标准化司、电子工业部科技与质量监督司公布为“技术规范指导性文件”。
    原始GB13000一直未被业界采用,后续国家标准GB18030技术上兼容GBK而非GB13000。


unicode(统一码)编码系统:
    可分为编码方式和实现方式两个层次。
    目前实际应用的统一码版本对应于UCS-2,使用16位的编码空间。也就是每个字符占用2个字节。
    这样理论上一共最多可以表示216(即65536)个字符。基本满足各种语言的使用。实际上目前版本的统一码并未完全使用
    这16位编码,而是保留了大量空间以作为特殊使用或将来扩展。

    最新(但未实际广泛使用)的统一码版本定义了16个辅助平面,两者合起来至少需要占据21位的编码空间,比3字节略少。
    但事实上辅助平面字符仍然占用4字节编码空间,与UCS-4保持一致。未来版本会扩充到ISO 10646-1实现级别3,
    即涵盖UCS-4的所有字符。UCS-4是一个更大的尚未填充完全的31位字符集,加上恒为0的首位,共需占据32位,即4字节。
    理论上最多能表示2^31个字符,完全可以涵盖一切语言所用的符号。
    注:
        通用字符集Universal Character Set,UCS,即是由ISO制定的ISO 10646
        (或称ISO/IEC 10646)标准所定义的标准字符集。 采用4byte编码.


    基本多文种平面的字符的编码为U+hhhh,其中每个h 代表一个十六进制数字,与UCS-2编码完全相同。
    而其对应的4字节UCS-4编码后两个字节一致,前两个字节则所有位均为0。

    unicode和UCS只是分配整数给字符的编码表;位字符定义了顺序;
    utf-8, utf-16 是对unicode的编码方法存储的压缩实现;
    就像gb2312区位码只定义了94区94位,存储实现的时候为了兼容ASCII给每个字符+160;知识没有压缩数据;
Unicode和UTF-8之间的转换关系表
UCS-4编码UTF-8字节流
U+00000000 – U+0000007F 0xxxxxxx
U+00000080 – U+000007FF 110xxxxx 10xxxxxx
U+00000800 – U+0000FFFF 1110xxxx 10xxxxxx 10xxxxxx
U+00010000 – U+001FFFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
U+00200000 – U+03FFFFFF 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
U+04000000 – U+7FFFFFFF 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx

参考
http://zh.wikipedia.org/wiki/Unicode             http://blog.donews.com/holen/archive/2004/11/30/188182.aspx

平时我们使用windows时,记事本->另存为->“保存”-> “编码”的下拉菜单 中有4个选项,

分别是ANSI, Unicode, Unicode big edian, UTF-8; 稍解释下:
windows XP以后的系统ANSI在简体中文下对应的是GBK编码(请不要贸然否定这句话,我做过考证)
Unicode 统一编码,为了数据共享,
big edian, 大端字节序(高字节对应低位数据),windows默认是小端存储(低字节对应低位数据)

View Code
big endian表示大端存储(高字节存储低位数据),intel模式是小端little endian存储(高字节存储高位数据),比如gb2312编码规定是第1字节存放区码,第2存放位码;""的区码是16, 位码是1;那么用小端方式就是高字节对应1, 低字节对应16了;
什么叫高字节,低字节?
通俗讲:打开记事本,输入0123456,0在内存中对应的就是低字节,1在内存中对应的就是高字节;

UTF-8 Unicode编码的一种实现,节省了存储空间,相当于压缩了unicode编码的压缩方法;

utf-8与unicode的互转:

在实际生活中,经常遇到utf-8, ansi,unicode编码的转换;我们只需要掌握utf-8和unicode的互转,剩下的就很easy,后面你会体会到的

对于gbk字符集来说,我们只需关心u+00000000~u+0000ffff的范围就行了;其他的范围很少能遇到;
从Unicode 到utf8的转换关系表,我们得到下面的小端存储方式, Unicode 转 utf8的 伪代码:

if(数据的高字节==0, 低字节 < 0x80)
    输出 低字节;
else if(数据的高字节 < 0x08,低字节 >= 0x80)
    输出 B(1000 0000)|(数据的0~5位), B(1100 0000)|B(数据的6~11位) 
else
    输出 B(1000 0000)|B(数据的0~5位), B(1000 0000)|B(数据的6~11位), B(1110 0000)|B(数据的12~15)
--------------------------
if(data < 0x0080)
    printf("%c%c", data,0);
else if(data >= 0x0080 && data < 0x08ff)
    printf("%c%c",0x80 | data&0x3F, 0xC0 | (data&0X0FC0)>>6);
else
    printf("%c%c", 0x80|data&0x3F, 0x80|(data&0x0FC0)>>6, 0xE0|(data&0xF000)>>12);

 utf8转Unicode的伪代码:

if (d[i]<0x80)
    print d;
else if (d[i+1] > 0xC0)
    x=(d[i+1]&0x1F)<<6|d[i]&0x3F;
    printf x&0x00FF , (x&0xFF00)>>8;
else 
    x = (d[i+2]&0x0F)<<12|(d[i+1]&0x3F)<<6|d[i]&0x3F;
    printf x&0x00FF , (x&0xFF00)>>8;

对于utf8和gbk的互转,通过unicode这个桥梁实现;
gbk如何转unicode? 使用cmd/u&type系统调用;

unicode 如何转gbk? 使用系统调用type(默认情况/u开关时关闭的);

原文地址:https://www.cnblogs.com/mathzzz/p/2593493.html