计算机原理-IO(三)

CPU和存储器之间是如何相互之间通信的呢,IO性能的瓶颈又是在哪里,希望看了这节可以给你解答。

总线

目前软件架构都流行ESB企业总线来处理消息的相互流转,这个架构灵感就参考了计算机中的总线。所谓总线是各种功能部件之间传送信息的公共通信干线,它是由导线组成的传输线束。我们知道计算机有运算器,控制器,存储器,输入输出设备这五大组件,所以总线就是用来连接这些组件的导线。

按照计算机所传输的信息种类,计算机的总线可以划分为

  • 数据总线: 数据总线DB是双向三态形式的总线,即它既可以把CPU的数据传送到存储器或输入输出接口等其它部件,也可以将其它部件的数据传送到CPU。数据总线的位数是微型计算机的一个重要指标,通常与微处理的字长相一致。我们说的32位,64位计算机指的就是数据总线。
  • 地址总线: 地址总线AB是专门用来传送地址的,由于地址只能从CPU传向外部存储器或I/O端口,所以地址总线总是单向三态的,这与数据总线不同。地址总线的位数决定了CPU可直接寻址的内存空间大小
  • 控制总线:控制总线主要用来传送控制信号和时序信号。控制总线的传送方向由具体控制信号而定,一般是双向的,控制总线的位数要根据系统的实际控制需要而定。其实数据总线和控制总线可以共用。

总线也可以按照CPU内外来分类:

  • 内部总线:在CPU内部,寄存器之间和算术逻辑部件ALU与控制部件之间传输数据所用的总线称为片内部总线。
  • 外部总线:通常所说的总线指片外部总线,是CPU与内存RAM、ROM和输入/输出设备接口之间进行通讯的通路,也称系统总线。

控制芯片

主要控制CPU和存储器,I/O设备等进行交互。一般都集成在主板上,所以攒机也不能忽略主板的。

对于目前的计算机结构来说,控制芯片集成在主板上,典型的有南北桥结构和单芯片结构。与芯片相连接的总线可以分为前端总线(FSB)、存储总线、IQ总线,扩展总线等。

  • 南北桥芯片结构
    • 北桥芯片,它控制着CPU的类型,主板的总线频率,内存控制器,显示核心等。它直接与CPU、内存、显卡、南桥相连,所以它数据量非常大;
      • 前端总线:是将CPU连接到北桥芯片的总线。FSB的频率是指CPU和北桥之间的数据交换速度。速度越快,数据带宽越高,计算机性能越好;
      • 内存总线:是将内存连接到北桥芯片的总线。用于和北桥之间的通信;
      • 显卡总线:是将显卡连接到北桥芯片的总新。目前有AGP,PCI-E等接口。其实并没有显卡总线一说,一般认为属于I/O总线;
    • 南桥芯片,它主要负责外部接口和内部CPU的联系;
      • I/O总线:连接外部I/O设备连接到南桥的总线, 比如USB设备,ATA,SATA设备,以及一些扩展接口;
      • 扩展总线:主要是主板上提供的一些PCI,ISA等插槽;
  • 单芯片结构: 单芯片组主要是是取消了北桥,因为现在CPU中内置了内存控制器,不需要再通过北桥来控制,这样就能提高内存控制器的频率,减少延迟。而现在一些CPU还集成了显示单元。也使得显示芯片的频率更高,延迟更低。

频率=性能

数据带宽 = (总线频率*数据位宽)/ 8

(每秒传送次数*每次传送大小)/单位大小转换

外频:系统总线的工作频率,一般的电脑这个是连接其他部件工作的基础频率。

分频:外部IO频率比系统总线低,采用2/3或1/3分频的方式就能使得CPU和外设同步的工作了。

倍频:CPU比外频高,所以需要一个倍频来协调工作。

CPU工作频率=外频*倍频

内存总线频率:

因为内存总线频率不同,所以内存和CPU之间存在同步和异步两种工作方式。

  • 同步方式:内存总线频率和CPU外频相同。比如以前的PC133和P3处理器,他们以同步的方式工作在133MHZ下。而当你超频时就需要拥有更高总线频率的内存。当然也需要北桥芯片的支持。
  • 异步方式:内存总线频率和CPU外频不同。睡着CPU外频的提高,内存也必须更新,所以出现了异步的方式。比如P4 CPU外频为200MHz,而内存却可以使用DDR333来运行。同时异步方式也在超频时经常使用。一般来说会有一个内存异步比率。在BIOS中会有相应的选项。

从性能上来讲,同步方式的延迟要好于异步方式,这也是为什么以前会说P4 200外频的CPU要使用DDR400才能发挥最大功效。但这也不是绝对的。比如我的I5处理器CPU外频工作在100MHz,而我使用的DDR3-1600的总线频率在200MHz,虽然不同步,但是拥有更高的传输速率。所以不能一概而论。

外部IO:

前面主要介绍了系统总线和CPU与内存之间的通信,最后一部分简单介绍一下CPU和I/O设备是如何通信的。对于计算机来说输入输出设备也是五大组件。我们知道相对于CPU,I/O设备的工作频率要慢的很多。比如早期的PCI接口工作频率只有33MHz,硬盘的IDE-ATA6的传输速率也只有133MB/s。而现在的 SATA3接口速率能达到600MB/s。

I/O设备一般由机械部件和电子部件两部分组成。电子设备一般称为设备控制器,在计算机上一般以芯片的形式出现,比如我们前面介绍的南桥芯片。不同的控制器可以控制不同的设备。所以南桥芯片中包含了多种设备的控制器,比如硬盘控制器,USB控制器,网卡、声卡控制器等等。而通过总线以及卡槽提供和设备本身的连接。比如PCI,PCI-E,SATA,USB等。

每个控制器都有几个寄存器和CPU进行通信。通过写入这些寄存器,可以命令设备发送或接受数据,开启或关闭。而通过读这些寄存器就能知道设备的状态。因为寄存器数量和大小是有限的,所以设备一般会有一个RAM的缓冲区,来存放一些数据。比如硬盘的读写缓存,显卡的显存等。一方面提供数据存放,一方面也是提高I/O操作的速度。

现在的问题是CPU如何和这些设备的寄存器或数据缓冲区进行通信呢?存在两个可选方案:

  1. 为每个控制器分配一个I/O端口号,所有的控制器可以形成一个I/O端口空间。存放在内存中。一般程序不能访问,而OS通过特殊的指令和端口号来从设备读取或是写入数据。早期计算机基本都是这种方式。
  2. 将所有控制器的寄存器映射到内存空间,于是每个设备的寄存器都有一个唯一的地址。这种称为内存映射I/O。

    另一种方式是两种的结合,寄存器拥有I/O端口,而数据缓冲区则映射到内存空间。Pentinum就是使用这种方式,所以在IBM-PC兼容机中,内存的0-640K是I/O端口地址,640K-1M的地址是保留给设备数据缓冲区的。(关于内存分布后面文章会介绍)

    对于我们程序员来说这两种方案有所不同

    1. 对于第一种方式需要使用汇编语言来操作,而第2种方式则可以使用C语言来编程,因为他不需要特殊的指令控制,对待I/O设备和其他普通数据访问方式是相同的。
    2. 对于I/O映射方式,不需要特殊的保护机制来组织对I/O的访问,因为OS已经完成了这部分工作,不会把这一段内存地址分配给其他程序。
    3. 对于内存可用的指令,也能使用在设备的寄存器上。

    任何技术有有点就会有缺点,I/O内存映射也一样:

    1. 前面提到过Cache可以对内存进行缓存,但是如果对I/O映射的地址空间进行缓存就会有问题。所以必须有机制来禁用I/O映射空间缓存,这就增大了OS的复杂性。
    2. 另一个问题是,因为发送指令后需要判断是内存还是I/O操作,所以它们需要能够检查全部的内存空间。以前CPU,内存和I/O设备在同一个总线上,所以检查很方便。但是后来为了提高CPU和内存效率,CPU和内存之间有一条高速的总线(比如QPI)。这样I/O设备就无法查看内存地址,因为内存地址总线旁落到了内存和CPU的高速总线上,所以需要一个额外的芯片来处理(北桥芯片,内存控制器的作用),增大了系统的复杂度。

 

瓶颈:1.寄存器-》内存(倍频-外频-内存频率)(普通应用可以忽略)

            2.IO瓶颈    IO速度实在太低(高性能程序都使用预先加载到内存的方式或者内存映射的方式来处理IO瓶颈:以时间,空间,金钱换取速度)

计算机原理(二)

不知道大家是否有过攒机的经历,攒机的时候,我们经常说不能只管 CPU 频率多高,性能多好,也要兼顾其他设备的性能,经常听到的词汇就是内存要大,总线频率也要匹配,硬盘转数也要跟上,这些其实都是涉及到不同设备部件之间的IO,一个不匹配就会拖累其他的。前面我们已经看了些CPU的皮毛,那么接下来我们就看下这些可能拖后腿的部件吧!

存储器

存储系统除了内存,硬盘这些,大概分为:寄存器、Cache、内部存储器、外部存储。请看如下图:

金字塔顶端的性能最好,既然这样,为啥还要分层呢,直接用L0就好了!

性价比。没有性价比,这些东西就不可能飞入寻常百姓家!我们知道,CPU是很快的,所以我们需要通过分层来平衡IO瓶颈,越接近CPU用越快的存储介质:各种晶体管,磁盘!

寄存器

寄存器基本与CPU是同频的,说明其高性能,但同时也意味着高能耗,高成本!所以我们只能少量的把寄存器集成到CPU中!

CASHE

SRAM,静态RAM,通常用于高速缓存,一般CPU中就集成了2级缓存就是这个!

DRAM ,这个其实就是我们熟悉的内存,这个容量大,DDR表示双倍的速率,而现在又有了DDR2,DDR3,他们的带宽也是越来越大。

上面的介质在断电后数据是会消失的,所以不能用来永久 存储数据,而ROM ,FLASH以及磁盘设备就是用来永久存储数据的,当然ROM 比磁盘速度快!

上面讲了这么多的存储设备,那么下一回,我们就来讲讲他们之间的通信,借此也可以了解看看以后如果写代码可能的瓶颈在哪里。

计算机原理(一)

大学毕业作也快两年了,回顾下,其实大学很多课程都是非常基础,对IT工作者而言其实是非常有作用的,特别是当要学得更加深入的时候,再去回顾下基础的东西是非常有必要的,所以就记录下回顾下计算机原理 的心得体会!

计算机之父

计算机发展史也不必刻意去多写了,我们都说冯诺依曼是计算机之父,其实图灵大大才是教父!

计算机组成

我们的计算机核心就是 :存储程式,然后执行!

下图就是一个基本结构流程:输入,存储,执行,输出。

对我们写代码的来说,比较重要的是存储与执行,因为这涉及到程序的性能如IO,计算速度等,我们最关心的就是CPU与内存了!程序数据是存储在硬盘里面,而如果要执行,一般来说我们需要把程序启动,把执行指令加载到内存中(数据交互更快)缓存着,然后CPU执行内存中的指令运算得出结果输出!

指令集

我们都知道,计算机是只能识别0跟1的(不通电OR通电),但是如果只是使用 0跟1进行编程,OMG,我想我们都要晕了,所以就出现了指令,我们装机买CPU的时候,都会听到指令集这么个说法,指令集跟牛逼啥的,其实就对应了我们这里说的0和1的一些组合,每个指令集可以然CPU执行一个(或者一组【牛逼的指令集】)操作,越牛逼的指令集在相同硬件条件下,能够让计算机速度更快!

汇编语言

二进制指令集虽然可以让CPU识别并执行,但是作为人类,还是太难懂了,所以就出现汇编语言,他用符号来映射具体的指令集,可读性进一步提升!那么把汇编语言转化为指令集,就需要一个辅助工具,汇编器

高级语言

计算机技术要普及,汇编语言显然还是太晦涩难懂,并且跟具体的硬件还有太多耦合,不同CPU可能只能识别不同的指令集,这样就需要写不同的汇编程序去实现,为了生产力的发展,于是不断出现各种高级语言,如C,C++,JAVA,C#…,从前面我们知道,高级语言也是不能被计算机识别的,所以我们就要编译器将高级语言转换成汇编程序,不同CPU硬件,我们就用不同编译器进行转换成不同的汇编程序,而不需要写不同的程序,编译器帮我们把硬件的差异屏蔽!

高级语言之间的差异

C,C++这种语言,其实是可以跨平台的,但是所写的程序代码,在不同平台(硬件)就需要不同编译器从新编译过才能执行。那么是不是有语言是可以做到写一次代码,编译一次代码就可以到处运行了呢!答案是肯定的,根据前面的一些讲解,你应该猜到了,这中间肯定还要加一层东西吧?答案是肯定的,JVM 虚拟机就是这么个中间的东西,JAVA语言写的代码,是先编译成中间代码的,然后由JVM负责解释执行。因为是在运行的时候解释执行的,所以性能上会有所下降,当然这提高了生产力,所以性能下降算个P啊(当然这只是针对大多数普通程序来说)!当然还有更加牛逼的脚本语言,都不需要编译成中间代码,直接依靠解释器就执行起来了,这又叫脚本语言,JS,大蟒蛇…说实在,也不慢啊!

本来想说计算机原理来着,顺便扯了些语言的东西,我们还是回来看CPU的是如何工作的吧:

CPU

有前面的图我们看到,CPU主要是由运算器与控制器组成的,控制器负责指令的,运算器接收指令进行运算处理,如果就是看上去那么简单,那么就太小看CPU了,实际CPU的组成运算比这个复杂多了,我们来看看Intel奔腾处理器的详细组成:

好晕啊,我来来具体分析下组要部件运算器与控制器的组成:

 控制器由程序计数器、指令寄存器、指令译码器、时序产生器和操作控制器组成。它是计算机指挥系统,完成计算机的指挥工作。尽管不同计算机的控制器结构上有很大的区别,当就其基本功能而言,具有如下功能:

    • 取指令 从内存中取出当前指令,并生成下一条指令在内存中的地址。 
    • 分析指令 指令取出后,控制器还必须具有两种分析的功能。一是对指令进行译码或测试,并产生相应的操作控制信号,以便启动规定的动作。比如一次内存读/写操作,一个算术逻辑运算操作,或一个输入/输出操作。二是分析参与这次操作的各操作数所在的地址,即操作数的有效地址。 
    • 执行指令 控制器还必须具备执行指令的功能,指挥并控制CPU、内存和输入/输出设备之间数据流动的方向,完成指令的各种功能。 
    • 发出各种微操作命令 在指令执行过程中,要求控制器按照操作性质要求,发出各种相应的微操作命令,使相应的部件完成各种功能。 
    • 改变指令的执行顺序 在编程过程中,分支结构、循环结构等非顺序结构的引用可以大大提供编程的工作效率。控制器的这种功能可以根据指令执行后的结果,确定下一步是继续按原程序的顺序执行,还是改变原来的执行顺序,而转去执行其它的指令。 
    • 控制程序和数据的输入与结果输出 这实际也是一个人机对话的设计,通过编写程序,在适当的时候输入数据和输出程序的结果。 
    • 对异常情况和某些请求的处理 当计算机正在执行程序的过程中,发生了一些异常的情况,例如除法出错、溢出中断、键盘中断等。

运算器的组成和功能: 运算器由算术逻辑单元(ALU)、累加寄存器、数据缓冲寄存器和状态条件寄存器组成,它是数据加工处理部件,完成计算机的各种算术和逻辑运算。相对控制器而言,运算器接受控制器的命令而进行动作,即运算器所进行的全部操作都是由控制器发出的控制信号来指挥的,所以它是执行部件。运算器有两个主要功能:

  • 执行所有的算术运算,如加、减、乘、除等基本运算及附加运算;
  • 执行所有的逻辑运算,并进行逻辑测试,如与、或、非、零值测试或两个值的比较等。

总结起来就是如下的一个流程:

光看CPU貌似我们已经了解了基本原理,其实最需要了解的其实是各个模块之间的交互,因为外部之间的IO是最容易产生瓶颈与疑惑的,所以接下来我们得学习下 CPU与内存及其他设备的交互等!

编码与字符

字符(集):中

字节:对应的二进制流byte

字符串(编码):字符与字节的映射关系:ANSI(多字节)字符串,Unicode (宽字节)字符串,(ANSI:0.一个字符可能是多个字节存储,1.不通国家字符集不一样,会有相同的字节映射,2.同一字符,不通编码规范 也可能对应不同的字符。)(Unicode :0.不同国家的字符集,不会有冲突的字节映射,是唯一的1.不同编码规范,最终可能对应不同的字符(为了省字节会添加一些识别信息,但去掉这些识别信息最终的值是一样的))

编码规范:同一编码的不通实现方式:UTF-8:可变长,节省Unicode 编码字符的空间。

编码转换:

把 UNICODE 字符串通过 ANSI 编码转化为“字节串”时,根据各自编码的规定,一个 UNICODE 字符可能转化成一个字节或多个字节。
反之,将字节串转化成字符串时,也可能多个字节转化成一个字符。比如,[0xD6, 0xD0] 这两个字节,通过 GB2312 转化为字符串时,将得到 [0x4E2D] 一个字符,即 ‘中’ 字。
“ANSI 编码”的特点:
1. 这些“ANSI 编码标准”都只能处理各自语言范围之内的 UNICODE 字符。
2. “UNICODE 字符”与“转换出来的字节”之间的关系是人为规定的。

比较懒,转一篇文章吧。

今天中午,我突然想搞清楚Unicode和UTF-8之间的关系,于是就开始在网上查资料。

结果,这个问题比我想象的复杂,从午饭后一直看到晚上9点,才算初步搞清楚。

下面就是我的笔记,主要用来整理自己的思路。但是,我尽量试图写得通俗易懂,希望能对其他朋友有用。毕竟,字符编码是计算机技术的基石,想要熟练使用计算机,就必须懂得一点字符编码的知识。

1. ASCII码

我们知道,在计算机内部,所有的信息最终都表示为一个二进制的字符串。每一个二进制位(bit)有0和1两种状态,因此八个二进制位就可以组合出256种状态,这被称为一个字节(byte)。也就是说,一个字节一共可以用来表示256种不同的状态,每一个状态对应一个符号,就是256个符号,从0000000到11111111。

上个世纪60年代,美国制定了一套字符编码,对英语字符与二进制位之间的关系,做了统一规定。这被称为ASCII码,一直沿用至今。

ASCII码一共规定了128个字符的编码,比如空格”SPACE”是32(二进制00100000),大写的字母A是65(二进制01000001)。这128个符号(包括32个不能打印出来的控制符号),只占用了一个字节的后面7位,最前面的1位统一规定为0。

2、非ASCII编码

英语用128个符号编码就够了,但是用来表示其他语言,128个符号是不够的。比如,在法语中,字母上方有注音符号,它就无法用ASCII码表示。于是,一些欧洲国家就决定,利用字节中闲置的最高位编入新的符号。比如,法语中的é的编码为130(二进制10000010)。这样一来,这些欧洲国家使用的编码体系,可以表示最多256个符号。

但是,这里又出现了新的问题。不同的国家有不同的字母,因此,哪怕它们都使用256个符号的编码方式,代表的字母却不一样。比如,130在法语编码中代表了é,在希伯来语编码中却代表了字母Gimel (ג),在俄语编码中又会代表另一个符号。但是不管怎样,所有这些编码方式中,0–127表示的符号是一样的,不一样的只是128–255的这一段。

至于亚洲国家的文字,使用的符号就更多了,汉字就多达10万左右。一个字节只能表示256种符号,肯定是不够的,就必须使用多个字节表达一个符号。比如,简体中文常见的编码方式是GB2312,使用两个字节表示一个汉字,所以理论上最多可以表示256×256=65536个符号。

中文编码的问题需要专文讨论,这篇笔记不涉及。这里只指出,虽然都是用多个字节表示一个符号,但是GB类的汉字编码与后文的Unicode和UTF-8是毫无关系的。

3.Unicode

正如上一节所说,世界上存在着多种编码方式,同一个二进制数字可以被解释成不同的符号。因此,要想打开一个文本文件,就必须知道它的编码方式,否则用错误的编码方式解读,就会出现乱码。为什么电子邮件常常出现乱码?就是因为发信人和收信人使用的编码方式不一样。

可以想象,如果有一种编码,将世界上所有的符号都纳入其中。每一个符号都给予一个独一无二的编码,那么乱码问题就会消失。这就是Unicode,就像它的名字都表示的,这是一种所有符号的编码。

Unicode当然是一个很大的集合,现在的规模可以容纳100多万个符号。每个符号的编码都不一样,比如,U+0639表示阿拉伯字母Ain,U+0041表示英语的大写字母A,U+4E25表示汉字”严”。具体的符号对应表,可以查询unicode.org,或者专门的汉字对应表

4. Unicode的问题

需要注意的是,Unicode只是一个符号集,它只规定了符号的二进制代码,却没有规定这个二进制代码应该如何存储。

比如,汉字”严”的unicode是十六进制数4E25,转换成二进制数足足有15位(100111000100101),也就是说这个符号的表示至少需要2个字节。表示其他更大的符号,可能需要3个字节或者4个字节,甚至更多。

这里就有两个严重的问题,第一个问题是,如何才能区别Unicode和ASCII?计算机怎么知道三个字节表示一个符号,而不是分别表示三个符号呢?第二个问题是,我们已经知道,英文字母只用一个字节表示就够了,如果Unicode统一规定,每个符号用三个或四个字节表示,那么每个英文字母前都必然有二到三个字节是0,这对于存储来说是极大的浪费,文本文件的大小会因此大出二三倍,这是无法接受的。

它们造成的结果是:1)出现了Unicode的多种存储方式,也就是说有许多种不同的二进制格式,可以用来表示Unicode。2)Unicode在很长一段时间内无法推广,直到互联网的出现。

5.UTF-8

互联网的普及,强烈要求出现一种统一的编码方式。UTF-8就是在互联网上使用最广的一种Unicode的实现方式。其他实现方式还包括UTF-16(字符用两个字节或四个字节表示)和UTF-32(字符用四个字节表示),不过在互联网上基本不用。重复一遍,这里的关系是,UTF-8是Unicode的实现方式之一。

UTF-8最大的一个特点,就是它是一种变长的编码方式。它可以使用1~4个字节表示一个符号,根据不同的符号而变化字节长度。

UTF-8的编码规则很简单,只有二条:

1)对于单字节的符号,字节的第一位设为0,后面7位为这个符号的unicode码。因此对于英语字母,UTF-8编码和ASCII码是相同的。

2)对于n字节的符号(n>1),第一个字节的前n位都设为1,第n+1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的unicode码。

下表总结了编码规则,字母x表示可用编码的位。

Unicode符号范围 | UTF-8编码方式
(十六进制) | (二进制)
——————–+———————————————
0000 0000-0000 007F | 0xxxxxxx
0000 0080-0000 07FF | 110xxxxx 10xxxxxx
0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

跟据上表,解读UTF-8编码非常简单。如果一个字节的第一位是0,则这个字节单独就是一个字符;如果第一位是1,则连续有多少个1,就表示当前字符占用多少个字节。

下面,还是以汉字”严”为例,演示如何实现UTF-8编码。

已知”严”的unicode是4E25(100111000100101),根据上表,可以发现4E25处在第三行的范围内(0000 0800-0000 FFFF),因此”严”的UTF-8编码需要三个字节,即格式是”1110xxxx 10xxxxxx 10xxxxxx”。然后,从”严”的最后一个二进制位开始,依次从后向前填入格式中的x,多出的位补0。这样就得到了,”严”的UTF-8编码是”11100100 10111000 10100101″,转换成十六进制就是E4B8A5。

6. Unicode与UTF-8之间的转换

通过上一节的例子,可以看到”严”的Unicode码是4E25,UTF-8编码是E4B8A5,两者是不一样的。它们之间的转换可以通过程序实现。

在Windows平台下,有一个最简单的转化方法,就是使用内置的记事本小程序Notepad.exe。打开文件后,点击”文件”菜单中的”另存为”命令,会跳出一个对话框,在最底部有一个”编码”的下拉条。

bg2007102801.jpg

里面有四个选项:ANSI,Unicode,Unicode big endian 和 UTF-8。

1)ANSI是默认的编码方式。对于英文文件是ASCII编码,对于简体中文文件是GB2312编码(只针对Windows简体中文版,如果是繁体中文版会采用Big5码)。

2)Unicode编码指的是UCS-2编码方式,即直接用两个字节存入字符的Unicode码。这个选项用的little endian格式。

3)Unicode big endian编码与上一个选项相对应。我在下一节会解释little endian和big endian的涵义。

4)UTF-8编码,也就是上一节谈到的编码方法。

选择完”编码方式”后,点击”保存”按钮,文件的编码方式就立刻转换好了。

7. Little endian和Big endian

上一节已经提到,Unicode码可以采用UCS-2格式直接存储。以汉字”严”为例,Unicode码是4E25,需要用两个字节存储,一个字节是4E,另一个字节是25。存储的时候,4E在前,25在后,就是Big endian方式;25在前,4E在后,就是Little endian方式。

这两个古怪的名称来自英国作家斯威夫特的《格列佛游记》。在该书中,小人国里爆发了内战,战争起因是人们争论,吃鸡蛋时究竟是从大头(Big-Endian)敲开还是从小头(Little-Endian)敲开。为了这件事情,前后爆发了六次战争,一个皇帝送了命,另一个皇帝丢了王位。

因此,第一个字节在前,就是”大头方式”(Big endian),第二个字节在前就是”小头方式”(Little endian)。

那么很自然的,就会出现一个问题:计算机怎么知道某一个文件到底采用哪一种方式编码?

Unicode规范中定义,每一个文件的最前面分别加入一个表示编码顺序的字符,这个字符的名字叫做”零宽度非换行空格”(ZERO WIDTH NO-BREAK SPACE),用FEFF表示。这正好是两个字节,而且FF比FE大1。

如果一个文本文件的头两个字节是FE FF,就表示该文件采用大头方式;如果头两个字节是FF FE,就表示该文件采用小头方式。

8. 实例

下面,举一个实例。

打开”记事本”程序Notepad.exe,新建一个文本文件,内容就是一个”严”字,依次采用ANSI,Unicode,Unicode big endian 和 UTF-8编码方式保存。

然后,用文本编辑软件UltraEdit中的”十六进制功能”,观察该文件的内部编码方式。

1)ANSI:文件的编码就是两个字节”D1 CF”,这正是”严”的GB2312编码,这也暗示GB2312是采用大头方式存储的。

2)Unicode:编码是四个字节”FF FE 25 4E”,其中”FF FE”表明是小头方式存储,真正的编码是4E25。

3)Unicode big endian:编码是四个字节”FE FF 4E 25″,其中”FE FF”表明是大头方式存储。

4)UTF-8:编码是六个字节”EF BB BF E4 B8 A5″,前三个字节”EF BB BF”表示这是UTF-8编码,后三个”E4B8A5″就是”严”的具体编码,它的存储顺序与编码顺序是一致的。

9. 延伸阅读

* The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets(关于字符集的最基本知识)

* 谈谈Unicode编码

* RFC3629:UTF-8, a transformation format of ISO 10646(如果实现UTF-8的规定)

(完)