原文地址:http://blog.laofu.online/2017/08/22/encode-string/
ASCII 的由来
在计算机的“原始社会”,有人想把日常的使用的语言使用计算机来表示, 我们知道在计算机的世界里面,只有0和1,为了解决尽量多的去表示字符,最终他们决定用8位0和1(一个字节)来表示字符,并且规定当机器读到这几个数据的时候,做出动作或者打印出指定的字符:
遇上0001 0000, 终端就换行;
遇上0000 0111, 终端就向人们嘟嘟叫;
遇上0001 1011, 打印机就打印反白的字,或者终端就用彩色显示字母。
这样就形成了最早期的ASCII码表,并把小于32的字符称为“控制字符”,剩下的继续进行编写,一直到127个字符,这样一套完整的字符方案就完成了,终于可以把文字搬到计算机中了。 下图是对应的ASCII码表
当大家都在兴奋的可以在电脑的阅读的文章的时候,新的问题又出现了,随着计算机的普及,很多国家都使用了计算机,原来的ASCII码在使用英语的国家可以无障碍的使用,但到了其它国家就无法满足要求了,所以他们决定对后面没有用到的编码(128-255)表示自己国家的语言,并加上了其它的相关的符号,直到编码空间被全部用完,从128-255的字符集称为“ASCII的扩展字符集”。
汉字怎么办?
等到中国人使用电脑的时候,发现已经没有编码供我们来存储对应的汉字了,连扩展的空间都已经被全部占满了。所以我们的前辈们用自己的智慧创造性提出了新的编码格式:
1. 去除127之外的乱七八糟的字符串和符号
2. 如果一个字节且小于127号的字符,与原ASCII码意义相同
3. 如果有两个同时大于127的字符则表示一个汉字,所以就会有一个字符会占用两个字节的说法
由于汉字占用两个字节,为了方便区别,我们把前面的字节称为“高位”(从161-247),后面的为“低位”(161-254),这样的一个搭配能表示出大概7000多个数字, 这个编码把所有用到的数字,符号,标点等等都编写进去,其中也有包括对ASCII码的重写,这样就会有我们常说的全角和半角的符号之分。这样的编码格式就是我们常说的“GB2312”
GB2312对照表
博大精深的文化带来的编码麻烦
当我们庆幸我们的汉字可以在计算中进行传递时,却发现GB2312编码小小的内存空间无法表示“博大精深文化” 需求,很多生僻字无法打印。所以不得不对现有的编码格式就行扩容,在剩余高位和低位的空间中寻找还没有使用的编码,但后来发现还是不够用,因为我们还有繁体字。
经过几次折腾,最终决定放弃对低位的限制,不再要求低位是大于127的值,一旦发现高位是大于127的就认为是汉字,这样又多了大概20000个汉字的表示,这种扩展的编码格式称为:GBK2312
再后来我们发现光有汉字是无法把“中国的文字” 全部表示出来,我们还要表示其它民族的文字,所以又对原来的编码格式进行扩展,最终形成编码:GB18030
Unicode的诞生
随着计算机的普及,世界很多国家和很多民族都使用了计算机,各种编码格式也越来越多。随着相互之间的文字交流也越来越频繁,编码格式是成为了主要的障碍,是时候是时候需要一个更为兼容性强大的的编码格式来一统天下,这个编码就是Unicode,创建它的是就是ISO(国际标谁化组织)。ISO当然解决这个问题也比较简单粗暴:
1. 废除所有地区性的编码
2. 重新制定一个能够包含地球上所有的编码
在Unicode的制定的时候,硬件已经不再那么的昂贵,所以就大气的把原来的使用一个字节表示(8位),改用成两个字符(16位)表示,其它的字符统一编码,这样理论上一共最多可以表示2^16(即65536)个字符,基本满足各种语言的使用。实际上当前版本的统一码并未完全使用这16位编码,而是保留了大量空间以作为特殊使用或将来扩展。这种大气的方式,虽然统一的编码方式,但也同样带来了问题,由于扩展的字节数,使用原来能用一个字节表示的字母和半角字符的高位都是0,存储空间比原来多了一倍。同样Unincode中,所有的字符都是都是两个字节 而汉字也由原来的两个字符转成了一个字符 –(更多的实现方式不属于此文的讨论之内)
在ASCII码的代码,一个字节是8位,用一个字节来表示一个字符,而汉字用两个字节来表示,所以有汉字可以作为两个字符。
而在Unicode时代,一个字节还是8位,但是所有的字符表示都是用两个字节,所以汉字也就算成一个字符
上述16位统一码字符构成基本多文种平面。最新(但未实际广泛使用)的统一码版本定义了16个辅助平面,两者合起来至少需要占据21位的编码空间,比3字节略少。但事实上辅助平面字符仍然占用4字节编码空间,
与UCS-4保持一致。未来版本会扩充到ISO 10646-1实现级别3,即涵盖UCS-4的所有字符。UCS-4是一个更大的尚未填充完全的31位字符集,加上恒为0的首位,共需占据32位,即4字节。理论上最多能表示2^31个字符,
完全可以涵盖一切语言所用的符号。
相关资料:Unicode字符平面映射
字符在内存中的表示:
半角字符“A”的表示方法:
ASCII码: 01000001
Unicode编码:00000000 01000001
汉字“付”的表示方法
GBK编码:10111000 10110110
Unicode编码:01001110 11011000
UTF8的出现
伴随着Unicode的缺点的出现,特别是到互联网的出现,为解决unicode如何在网络上传输的问题,可谓各种实现方法都出现了,具有代表性的就是UTF8和UTF16。这里强调:他们的关系是UTF8和UTF16是Unicode的一种实现方式。
那UTF8又是如何表示字符的呢? UTF8的最大的特点是可伸缩性,具体的编码规则:
1. 对于一个单字节字符,而且首位为0 ,其余的用Unicode补齐。所以对于ASCII内的字符,ASCII的编码与UTF8相同。
2. 对于n字节的字符,第一个字节的前n位都设为1,第n+1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的unicode码。
还是以“付”为例:
汉字“付”的表示方法
GBK编码:10111000 10110110
Unicode编码:01001110 11011000
UTF-8编码:11100100 10111000 10100101
在读取UTF编码的时候,我们只需要读取前面几位有几个1 ,就可以清楚的知道,当前字符占用了几个字节。至于编码的转换,如果理解了原理就很简单了,如果有机会我会写出如何手动去转换字符编码 –(如果我还有动力的写下篇的话~)
.net代码实现一个字符编码转换,这里转出来是二进制的数据,其它格式可以自己扩展:
public static void Main(string[] args)
{
ConvertEncode("Unicode", "GBK", "付");
Console.Read();
}
private static void ConvertEncode(string tarEncode, string desEncode, string content, string defaultEncode = "Unicode")
{
var byteArr = Encoding.GetEncoding(defaultEncode).GetBytes(content);
var str = Encoding.Convert(Encoding.GetEncoding(tarEncode), Encoding.GetEncoding(desEncode), byteArr);
foreach (var b in str)
{
Console.Write(Convert.ToString(b, 2));
}
}
【参考资料】
-
维基百科-Unicode
-
阮一峰的网络日志-字符编码笔记:ASCII,Unicode和UTF-8
-
ASCII、Unicode、GBK和UTF-8字符编码的区别联系