字符编码探密--ASCII,UTF8,GBK,Unicode

目录

ASCII 的由来

在计算机的“原始社会”,有人想把日常的使用的语言使用计算机来表示, 我们知道在计算机的世界里面,只有0和1,为了解决尽量多的去表示字符,最终他们决定用8位0和1(一个字节)来表示字符,并且规定当机器读到这几个数据的时候,做出动作或者打印出指定的字符:

遇上0001 0000, 终端就换行;				

遇上0000 0111, 终端就向人们嘟嘟叫;		

遇上‭‭0001 1011‬, 打印机就打印反白的字,或者终端就用彩色显示字母。

这样就形成了最早期的ASCII码表{:target=”_blank”},并把小于32的字符称为**“控制字符”**,剩下的继续进行编写,一直到127个字符,这样一套完整的字符方案就完成了,终于可以把文字搬到计算机中了。

  当大家都在兴奋的可以在电脑的阅读的文章的时候,新的问题又出现了,随着计算机的普及,很多国家都使用了计算机,原来的ASCII码在使用英语的国家可以无障碍的使用,但到了其它国家就无法满足要求了,所以他们决定对后面没有用到的编码(128-255)表示自己国家的语言,并加上了其它的相关的符号,直到编码空间被全部用完,从128-255的字符集称为“ASCII的扩展字符集”。

汉字怎么办?

  等到中国人使用电脑的时候,发现已经没有编码供我们来存储对应的汉字了,连扩展的空间都已经被全部占满了。所以我们的前辈们用自己的智慧创造性提出了新的编码格式:


 1. 去除127之外的乱七八糟的字符串和符号    

 2. 如果一个字节且小于127号的字符,与原ASCII码意义相同    

 3. 如果有两个同时大于127的字符则表示一个汉字,所以就会有一个字符会占用两个字节的说法   

  由于汉字占用两个字节,为了方便区别,我们把前面的字节称为“高位”(从161-247),后面的为“低位”(161-254),这样的一个搭配能表示出大概7000多个数字, 这个编码把所有用到的数字,符号,标点等等都编写进去,其中也有包括对ASCII码的重写,这样就会有我们常说的全角和半角的符号之分。这样的编码格式就是我们常说的“GB2312

GB2312对照表{:target=”_blank”}

博大精深的文化带来的编码麻烦

  当我们庆幸我们的汉字可以在计算中进行传递时,却发现GB2312编码小小的内存空间无法表示“博大精深文化” 需求,很多生僻字无法打印。所以不得不对现有的编码格式就行扩容,在剩余高位和低位的空间中寻找还没有使用的编码,但后来发现还是不够用,因为我们还有繁体字。

  经过几次折腾,最终决定放弃对低位的限制,不再要求低位是大于127的值,一旦发现高位是大于127的就认为是汉字,这样又多了大概20000个汉字的表示,这种扩展的编码格式称为:GBK2312

   再后来我们发现光有汉字是无法把“中国的文字” 全部表示出来,我们还要表示其它民族的文字,所以又对原来的编码格式进行扩展,最终形成编码:GB18030

Unicode的诞生

随着计算机的普及,世界很多国家和很多民族都使用了计算机,各种编码格式也越来越多。随着相互之间的文字交流也越来越频繁,编码格式是成为了主要的障碍,是时候是时候需要一个更为兼容性强大的的编码格式来一统天下,这个编码就是Unicode{:target=”_blank”},创建它的是就是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字符平面映射{:target=”_blank”}


半角字符“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));
      }
  }

【参考资料】

  1. 维基百科-Unicode{:target=”_blank”}

  2. 阮一峰的网络日志-字符编码笔记:ASCII,Unicode和UTF-8{:target=”_blank”}

  3. ASCII、Unicode、GBK和UTF-8字符编码的区别联系{:target=”_blank”}

IIS执行原理

服务器的监听(IIS6.0+版本)

  1. 当请求到达服务器时,请求最终会到达TCPIP.SYS驱动程序,TCPIP.SYS将请求转发给HTTP.SYS网络驱动程序的请求队列中(可以理解为专门处理http请求的进程),当然在处理请求的过程中,HTTP.SYS进程会维护一个配置表用缓存请求的url和和应用程序池对应的关系。

  2. 当一个http请求被捕获到,HTTP.SYS会读取配置表,如果对应的应用程序没有启动,则HTTP.SYS会启动IIS相对应的应用程序。具体运行机制可以理解成为:

HTTP.SYS

HTTP.SYS是TCP之上的一个网络驱动程序,因此,HTTP.SYS不再属于IIS(这里说的IIS都是IIS6.0+版本,下文如果不特殊指明,默认为IIS6.0+版本),它已经从IIS中独立了出来。 Http.Sys独立有以下几个优点:

  • 可靠性: HTTP.SYS运行在内核模式下,作为操作系统的驱动程序运行。因此,HTTP.SYS不会受到用户代码的影响,它始终处于稳定运行状态,对用户的http请求进行监听,并及时作出反应。

  • 高性能: 从用户发送http请求到系统返回响应结果的这一过程都是HTTP.SYS在内核模式下完成的。不需要在内核模式和用户模式下进行切换,这样就极大地节省了系统资源,提高了请求的响应速度。

IIS处理

W3SVC

  1. W3SVC服务是一个独立运行的程序,寄宿在svchost.exe进程中,负责用户的参数监视和重新启动应用池的工作。 当一个请求进入HTTP.SYS的队列中,会通知W3SVC服务根据IIS中的配置去创建对应的应用进程,进行处理。

W3WP.exe

  1. 当HTTP.SYS把请求传递给IIS时候,W3SVC会启动对应的应用程序池
  2. 当用户请求的是静态文件,如:HTML和图片等,IIS会直接读取文件内容,转成二进制文件流,返回给HTTP.SYS。
  3. 当请求非静态文件,如:.aspx。
  • 3-1. w3wp.exe会根据IIS中ISAPI扩展读取对应的处理的Dll,用asp.net举例:当用户访问的网站是asp.net平台,则 类型是.cshtml和.aspx文件类型。根据配置w3wp.exe会加载aspnet_isapi.dll(简称是ISAPI).

IIS中应用程序的映射:

IIS中处理流程:

  • 3-2. 当ISAPI加载后,会启动一个ASP.NET的工作进程,把信息的控制权交给Asp.Net来处理。此处请求的处理由IIS交给了asp.net的程序。

    基于对上面的说明,可以把IIS的处理过程理解表示如下图:

说到这里,把IIS请求的流程简单的做了说明,后面的工作就由Asp.Net去完成了。

.Net程序的运行过程

说到Asp.Net的运行,不得不先说下.Net的运行机制(算是为后面的文章做一个铺垫)。
在vs中写了一段C#代码(或者其它.net平台的语言,此处简单的用C#来说明) ,编译器会把代码转译成IL的中间语言程序。当程序运行时,系统调用jit编译器,把中间语言编译成对应的cpu指令,等待cpu的最终调用。具体过程如下:

托管和非托管

  • 定义

    托管的概念是在.net框架诞生后出现的。用比较通俗的话解释就是运行在.net框架下,并受.net框架管理的应 用或其他组件称为托管的,反之为非托管的。

  • 区别

    1、托管代码是一种中间语言,运行在CLR上;非托管代码被编译为机器码,运行在机器上。
    2、托管代码独立于平台和语言,能更好的实现不同语言平台之间的兼容;非托管代码依赖于平台和语言。
    3、托管代码可享受CLR提供的服务(如安全检测、垃圾回收等),不需要自己完成这些操作;非托管代码需要自己提供安全检测、垃圾回收等操作。

  • 性能
    对于这个问题,首先澄清.net中的JIT是不同Java中的JVM的(JVM是一个Interpreter,在运行时读取IL汇编代码,然后模拟成x86代码),在.Net中使用的是一种更高级的技术,在程序首次加载的时候,JIT是把代码编译成本地指令(这也就是为什么.Net程序首次运行很慢的原因,但你的程序不可能只跑一次,尤其是在服务器上面的程序!),.NET程序经JIT转换后与非托管程序运行一样了,直接由CPU执行。
    但对于JIT来说,恰恰由于是即时编译,对当前的环境认识的比非托管更为深刻(包括当前的CPU最新的指令),在编译时可以进行优化。而非托管代码,在编译的时候要保证兼容性,所以只能使用最通用的cpu指令(公共的CPU指令),所以我个人认为,.Net在执行的效率上更具有优势。
    非托管编译运行过程

托管代码编译运行过程

Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×