刨根究底字符编码之九——字符编码方案的演变与字节序

字符编码方案的演变与字节序

一、字符编码方案的演变

1.

前文已经提及,编号字符集CCS(简称字符集)与字符编码方式CEF(简称编码方式)这两个概念,在早期并没有必要严格区分。

在Unicode编码方案出现之前,字符集及其具体的编码方式是绑定耦合在一起的,因此,“字符集”、“编码”或“编码方式”甚至“编码方案”这几个概念经常相互指代、彼此混用。

比如,字符集里的字符编号(即码点编号)在很多文章里也称之为字符编码、字符码、码点、码位、码点值、码值等,字符编码也称之为编码实现、编码方案、编码方式、编码格式、编码形式、内码、编码值、码值(你没看错,字符编号与字符编码都有可能被简称为“码值”,头大了吧),等等,非常混乱。

2.

对于ASCII、GB2312、GBK、GB18030、Big5之类采用传统字符编码模型的历史遗留方案来说,由于基本上一个字符集只使用一种编码方式,因此这种混用问题还不大。

但在Unicode这样采用现代字符编码模型的全新方案出现之后,很多人受上述这些历史遗留方案的影响,从而导致无法正确地理解字符集和编码方式的关系,这导致了概念混乱,引起了大量误解。

然而,对于采用现代字符编码模型的Unicode标准来说,字符集和编码方式是必须明确区分的。从软件工程的角度来讲,传统字符编码模型中紧密绑定耦合在一起的字符集及编码方式这两个概念,在现代字符编码模型中被分离解耦了,而这种解耦带来了极大的灵活性。

这意味着,对于采用现代字符编码模型的同一个字符集,可以采用多个不同的编码方式对其字符编号进行编码。也因此,作为同一个Unicode字符集,目前就定义了UTF-8、UTF-16和UTF-32等(UTF,即Unicode/UCS Transformation Format)多种可选的编码方式。

3.

所以,用Unicode来称呼一个编码方式已不合适,并且容易产生误导,引发混乱和导致困惑,而应该用UTF-8、UTF-16和UTF-32来称呼编码方式,以Unicode来称呼字符集,将包括Unicode字符集及各UTF编码方式等在内的整体称之为字符编码方案或字符编码系统或字符编码标准。

另外,同一字符编码方式CEF的码元序列,在计算机实际处理、存储和传输时,还需再次编码转换为字符编码模式CES的字节序列。

字符编码方式CEF的码元序列可理解为字符编码的逻辑表示形式,相对而言,字符编码模式CES的字节序列则可理解为字符编码在计算机中的物理表示形式

而字节序列,则涉及到了不同的字节序(Byte-Order,主要分为大端序Big-Endian、小端序Little-Endian)。

二、字节序

1.

字节序,又称字节顺序,其英文为Byte-Order;另外英文中还可称为Endianness,因此也翻译为端序。

Endianness这个词,源自于1726年Jonathan Swift的名著:Gulliver‘s Travels(格列佛游记)。在书中有一个童话故事,大意是指Lilliput小人国的国王下了一道指令,规定其人民在剥水煮蛋时必须从little-end(小端)开始剥。这个规定惹恼了一群觉得应该要从big-end(大端)开始剥的人。事情发展到后来演变成了一场战争。后来,支持小端的人被称为little-endian,反之则被称为big-endian(在英语中后缀“-ian”表示“xx人”之意)。

1980年,Danny Cohen在他的论文“On Holy Wars and a Plea for Peace”中,第一次使用了Big-endian和Little-endian这两个术语,最终它们成为了异构计算机系统之间进行通讯、交换数据时所要考虑的极其重要的一个问题。

(注:所谓异构是指不同架构、不同结构、不同构造等,而这里的异构计算机系统,主要指的是采用不同CPU和/或不同操作系统的计算机系统。)

2.

为什么会存在字节序的问题?

当然不会是像童话故事里那样出于“无厘头”的原因,而是因为历史上设计不同计算机系统的人在当时基于各自的理由和原因(这里的理由和原因网上存在着各种不同的说法,但也或许根本就没有具体理由和原因,只是设计人员的个人偏好,甚至是随意决定的),在各自计算机系统的设计上作出了不同的选择。

字节序共分为三种:

  • 大端序BE(Big-Endian,也称高尾端序);
  • 小端序LE(Little-Endian,也称为低尾端序);
  • 中间序ME(Middle-Endian,也称为混合序),不太常用,本文不作介绍。

3.

字节序,具体来说,就是多字节数据(大于一个字节的数据)在计算机中存储、读取时其各个字节的排列顺序。

字节序也被称为端序,这里的“端”,是指多字节数据中位于两端的字节,很多情况下还特指尾端字节(也称为小端字节)。

所谓尾端字节或小端字节,假设按照人对文字通常从左到右(或从上到下)的读写顺序来看的话,多字节数据位于右端(或下端)的低位字节就是尾端字节或小端字节,而将位于左端(或上端)的高位字节称为头端字节或大端字节。

当然,如果不按照通常从左到右的顺序,而是按照从右到左的顺序,那么多字节数据位于右端的高位字节就是头端字节或大端字节,而将位于左端的低位字节称为尾端字节或小端字节。

可见,不论读写顺序如何,所谓大端、头端,指的是多字节数据中,代表更大数值的那个字节所在的那一端,而相反的那一端则是小端、尾端。

4.

而要彻底讲清楚大端序(高尾端序)、小端序(低尾端序),则需要从人读写二进制数的方向和内存地址的增长方向两者相结合讲起:

人读写二进制数的方向为(这是确定不变的)左--->右,大端/头端/高位--->小端/尾端/低位;或上--->下,大端/头端/高位--->小端/尾端/低位;

内存地址的增长方向则为(这是确定不变的)左--->右,低地址--->高地址;或上--->下,低地址--->高地址。

不过,计算机在内存中存取数据的方向则不是确定不变的,而是分为两种(注意,由于人的读写方向和内存地址增长方向是确定不变的,因此这里指的是计算机在内存中书写阅读数据的方向)

1) 左--->右,大端/头端/高位--->小端/尾端/低位;或上--->下,大端/头端/高位--->小端/尾端/低位;

这种情况下,站在人的读写方向和内存地址增长方向(这两者的方向刚好一致)的角度来看,则是:大端在左(或在上),所以称之为大端序;或者说尾端在内存高地址,所以称之为高尾端序(即内存高地址存放多字节数据的尾端字节的字节顺序)。

2) 右--->左,大端/头端/高位--->小端/尾端/低位;或下--->上,大端/头端/高位--->小端/尾端/低位。

这种情况下,站在人的读写方向和内存地址增长方向(这两者的方向刚好一致)的角度来看,则是:小端在左(或在上),所以称之为小端序;或者说尾端在内存低地址,所以称之为低尾端序(即内存低地址存放多字节数据的尾端字节的字节顺序)。

【特别提示:大端序、小端序特别容易搞混,不好记忆;因此,建议使用高尾端序、低尾端序,本人是按下面这个方法来记忆的,很容易记住:存储的数据分头和尾——左头右尾,内存的地址分低和高——左低右高;因此,“高尾端”表示内存的地址存储数据的尾端,“低尾端”表示内存的地址存储数据的尾端。】

5.

注意,这里的“数据”指的是数据类型意义上的数据,因此,准确地说字节序只跟多字节的整型数据类型有关,比如intshortlong型;跟单字节的整型数据类型byte无关。

那么,为什么就只跟多字节的整型数据有关,而跟单字节的整型数据无关呢?

因为在现代计算机中,字节是计算机数据存储的基本单位,对于整体上的单一字节(a byte),涉及到的是其8个比特位的顺序(位序、比特序,由于一般直接由硬件处理,程序员大致了解即可,这里不深入探讨),显然不存在字节序问题。

然而,对于在数据类型上作为一个整体的多字节数据而言,它是由各个可被单独寻址的字节所组成的(处理器寻址的最小单位一般是1个字节),由于历史的原因,其各个字节的存储顺序在不同的系统平台(包括CPU和操作系统)上是不同的。

6.

也就是说,如果计算机处理的数据是单字节数据类型(byte),是不存在字节序问题的,因为单个字节已经是处理器寻址的最小单位以及存储和传输的最小单元了,存储时单字节数据类型直接进行,读取时也不存在根据前后2个字节才能解析出其值的情况,而构造字节流时也不会从一个单字节数据类型的值当中产生2个或以上的字节(既然是单字节数据类型,构造字节流时当然只可能产生1个字节)。

但是,如果计算机处理的数据是多字节数据类型(int、short、long等),虽然由于构成它们的2个或2个以上的字节是作为一个整体来进行处理的(比如以汇编语言中的数据类型word或dword为单位进行一次性处理,而不是以byte为单位分次处理;更深入地来讲,CPU一般是以字为一个整体来处理数据的,当单个数据不足一个字长时,则将多个数据“拼成”一个字再进行处理),但问题是字节才是CPU对内存寻址的最小单位以及存储和传输的最小单元。

因此,CPU在读取作为一个整体来进行处理的多字节数据类型的数据时,必须根据前后2个或2个以上的字节来解析出一个多字节数据类型的值;而且构造字节序列时也会从一个多字节数据类型的值当中产生2个或2个以上的字节。

7.

这样一来,多字节数据类型的数据内部各字节间的排列顺序,是会影响从字节序列恢复到数值的;反之,也会影响从数值到字节序列的构造。

所以,在存储和读取多字节数据类型的数据时,必须按照计算机系统所规定的字节序进行(这一点程序员了解即可,计算机会自动处理);而尤其是在跨字节序不同的异构计算机系统进行通讯并交换数据时,通讯的任何一方更是必须明确对方所采用的字节序,然后双方将各自接收到的数据按各自的字节序对数据进行转换(有时候需要程序员专门编写转换程序),否则通讯将会出错,甚至直接失败。

8.

实际上,int、short、long等数据类型一般是编程语言层面的概念,更进一步而言,这其实涉及到了机器硬件层面(即汇编语言)中的数据类型byte字节、word字、dword双字等在硬件中的表达与处理机制(实质上字节序跟CPU寄存器的位数、存放顺序密切相关)。具体可参看附文:《本质啊本质之一:数据类型的本质》、《寄存器与字、字节》。

【附:本质啊本质之一:数据类型的本质

CSDN博客 博主:band_of_brothers 发表于:2007-10-10 22:20

研究一个层面的问题,往往要从更深的层面找寻答案。这就如C语言与汇编、汇编与机器指令,然而终究要有个底限,这个底限以能使我们心安理得为准,就好比公理之于数学、三大定律之于宏观物理。

在这里就将机器指令作为最后的底限吧,尽管再深入下去还有微指令,但那毕竟是太机器了,可以了。以下所有从C代码编译生成汇编代码用的是命令:cl xxx..c /Fa /Ze。

类型的本质

类型这个概念,好多地方都有讲,但说实话,你真的理解吗?什么是类型?类型是一个抽象的概念还是一个真实的存在?嗯?

开始:

1、“好多相同或相似事物的综合”(辞海)。

2、X86机器的数据类型有byte、word、dword、fword、tword、qword,等等。

3、“给内存块一个明确的名字,就象邮件上的收件人一样。给其一个明确的数据类型,就好象说,邮件是一封信,还是一个包裹。”

4、类型就是一次可以操作的块的大小,就是一个单位,就像克、千克、吨一样。双字一次操作32位;字,一次操作16位;如果没有各种类型,机器只有一个类型单位——字节,那么当需要一个4字节大小的块时,就需要4次操作,而如果有双字这个类型单位,那么只需要一次操作就可以了。

5、类型,是机器层面支持的,不是软的,是硬的,有实实在在的机器码为证。

类型的反汇编

W32dasm反汇编出来的东西,可以看出不同的类型,机器码不同,说明类型是机器硬件级别支持的,不是通过软件实现的,更不是一个抽象的概念。

Opcodes上关于mov的机器码讲的更清楚:

需要说明的是,一些大的类型单位,如qword等,在mov等标准指令里是没有的,在一些特殊指令里才能用到,如浮点指令:fmul qword ptr [0067FB08] 机器码:DC0D08FB6700。】

【附:寄存器与字、字节

字节:记为byte,一个字节由8个比特(bit)组成,可以直接存在一个8位寄存器里

1 0 1 0 1 0 0 1

一个字节

字:记为word,一个字由2个字节(共16比特)组成,可以直接存在一个16位寄存器里

1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0

高位字节        低位字节

一个8位寄存器用2位十六进制数表示,只能存1个字节(1个byte类型的数据)

一个16位寄存器用4位十六进制数表示,可存1个字(1个word类型的数据)或2个字节(2个byte类型的数据)

一个32位寄存器用8位十六进制数表示,可存2个字(1个dword类型的数据或2个word类型的数据)或4个字节(4个byte类型的数据)】

(笨笨阿林原创文章,转载请注明出处)

9.

下面简要介绍一下字节序的三种类型:

a) 小端序Little-Endian(低尾端序)

就是低位字节(即小端字节)存放在内存的低地址,而高位字节(即大端字节)存放在内存的高地址。

这是最符合人的直觉思维的字节序(但却不符合人的读写习惯),因为从人的第一观感来说,低位字节的值小,对应放在内存地址也小的地方,也即内存中的低位地址;反之,高位字节的值大,对应放在内存地址大的地方,也即内存中的高位地址。

b) 大端序Big-Endian(高尾端序)

就是高位字节(即大端字节)存放在内存的低地址,低位字节(即小端字节)存放在内存的高地址。

这是最符合人平时的读写习惯的字节序(但却不符合人的直觉思维),因为不用像在Little-EndIan中还需考虑字节的高位、低位与内存的高地址、低地址的对应关系,只需把数值按照人通常的书写习惯,从高位到低位的顺序直接在内存中从左到右或从上到下(下图中就是从上到下)按照由低到高的内存地址,一个字节一个字节地填充进去。

c) 中间序Middle-Endian(混合序Mixed-Endian

混合序具有更复杂的顺序。以PDP-11为例,32位的0x0A0B0C0D被存储为:

混合序较少见,常见的多为大端序和小端序。

【附:网络字节序(network byte order网络字节顺序、网络序)。

另外,还一种网络字节序(network byte order网络字节顺序、网络序)

网络字节顺序是TCP/IP中规定好的一种数据表示格式,它与具体的CPU类型、操作系统等无关,从而可以保证数据在不同主机之间传输时能够被正确解释。IP协议中定义大端序Big Endian为网络字节序。

不过,容易令人困惑的是,IP协议作为网络层协议,其面向的数据是报文,是没有字节的概念的,也就无关字节序了。因此,英文版wikipedia上说:

In fact, the Internet Protocol defines a standard big-endian network byte order. This byte order is used for all numeric values in the packet headers and by many higher level protocols and file formats that are designed for use over IP.

也就是说,IP协议里的字节序实际上是用在分组头里的数值上的,例如每个分组头会包含源IP地址和目标IP地址,在寻址和路由的时候是需要用到这些值的。

比如,4个字节的32 bit值以下面的次序传输:首先是高位的0~7bit,其次8~15bit,然后16~23bit,最后是低位的24~31bit。这种传输次序称作大端字节序。由于TCP/IP头部中所有的二进制整数在网络中传输时都要求以这种次序,因此它又称作网络字节序。

再比如,以太网头部中2字节的“以太网帧类型”字段,表示是的后面所跟数据帧的类型。对于ARP请求或应答的以太网帧类型来说,在网络传输时,发送的顺序是以大端方式进行的:0x08,0x06。其在内存中的映象如下所示:

内存低地址

------------------------

0x08 -- 高位字节

0x06 -- 低位字节

------------------------

内存高地址

该字段的值为0x0806,也是以大端方式存放在内存中的。

其实,IP报文中的很多数据都是需要做字节序转换的,比如包长度、check sum校验和等,这些值大都是short(16bit)或者long(32bit)型,所以解析IP报文时也需要做网络->主机字节序转换,而生成报文字节流时则需要进行主机->网络字节序转换(计算机中的字节序被称之为主机字节序,简称主机序;相对于网络传输中的字节序被称之为网络字节序,简称网络序)。

为了进行网络字节序与主机字节序的转换,BSD sockets(Berkeley sockets)提供了四个转换的函数:htons、ntohs、htonl、ntohl,其中h是host、n是network、s是short、l是long:

htons把unsigned short类型从主机字节序转换到网络字节序

htonl把unsigned long类型从主机字节序转换到网络字节序

ntohs把unsigned short类型从网络字节序转换到主机字节序

ntohl把unsigned long类型从网络字节序转换到主机字节序

在使用little endian的系统中,这些函数会把字节序进行转换;在使用big endian类型的系统中,这些函数会定义成空宏。

Windows系统API中也提供了类似的转换函数。而在.Net中,网络字节序与主机字节序两者之间的转换,由IPAddress类的静态方法提供:HostToNetworkOrder和NetworkToHostOrder。】

10.

Intel和AMD的X86平台,以及DEC(Digital Equipment Corporation,后与Compaq合并,之后Compaq又与HP合并)采用的是Little-Endian,而像IBM、Sun的SPARC采用的就是Big-Endian。有的嵌入式平台是Big-Endian的。JAVA字节序也是Big-Endian的。

当然,这不代表所有情况。有的CPU即能工作于小端,又能工作于大端,比如ARM、Alpha、摩托罗拉的Power PC、SPARC V9、MIPS、PA-RISC和IA64等体系结构(具体情形可参考处理器手册),其字节序是可切换的,这种可切换的特性可以提高效率或者简化网络设备和软件的逻辑。

这种可切换的字节序被称为Bi-Endian(前缀“Bi-”表示“双边的、二重的、两个的”),用于硬件上意指计算机存储时具有可以使用两种不同字节序中任意一种的能力。具体这类CPU是大端还是小端,和具体设置有关。如Power PC可支持Little-Endian字节序,但其默认配置为Big-Endian字节序。

11.

一般来说,大部分用户的操作系统,如windows、FreeBsd、Linux是Little-Endian的;少部分,如Mac OS是Big-Endian的。

具体参见下表:

12.

所以说,Little Endian还是Big Endian与操作系统和CPU芯片类型都有关系。因此在一个计算机系统中,有可能同时存在大端和小端两种模式的现象。

这一现象为系统的软硬件设计带来了不小的麻烦,这要求系统设计工程师必须深入理解大端和小端模式的差别。大端与小端模式的差别体现在一个处理器的寄存器、指令集、系统总线等各个层次中。

其实很多技术人员在实际的非跨平台桌面应用开发过程中都很少会直接和字节序打交道,但在跨平台及网络应用开发过程中因为涉及到异构计算机系统间的通讯交流,字节序是很难回避的问题。

(笨笨阿林原创文章,转载请注明出处)

时间: 2024-10-13 01:15:46

刨根究底字符编码之九——字符编码方案的演变与字节序的相关文章

刨根究底字符编码之十三——UTF-16编码方式

UTF-16编码方式 1. UTF-16编码方式源于UCS-2(Universal Character Set coded in 2 octets.2-byte Universal Character Set).而UCS-2,是早期遗留下来的历史产物. UCS-2将字符编号(即码点值)直接映射为字符编码(CEF,而非CES,详见前文中对现代字符编码模型的解释),亦即字符编号就是字符编码,中间没有经过特别的编码算法转换.因此,从现代字符编码模型的角度来看的话,此时并没有将编号字符集CCS与字符编码

刨根究底字符编码之十一——UTF-8编码方式与字节序标记

UTF-8编码方式与字节序标记 一.UTF-8编码方式 1. 接下来将分别介绍Unicode字符集的三种编码方式:UTF-8.UTF-16.UTF-32.这里先介绍应用最为广泛的UTF-8. 为满足基于ASCII.面向字节的字符处理的需要,Unicode标准中定义了UTF-8编码方式.UTF-8应该是目前应用最广泛的一种Unicode编码方式(但不是最早面世的,UTF-16要早于UTF-8面世).它是一种使用8位码元(即单字节码元)的变宽(即变长或不定长)码元序列的编码方式. 由于UTF-16对

【字符编码】Java字符编码详细解答及问题探讨

一.前言 继上一篇写完字节编码内容后,现在分析在Java中各字符编码的问题,并且由这个问题,也引出了一个更有意思的问题,笔者也还没有找到这个问题的答案.也希望各位园友指点指点. 二.Java字符编码 直接上代码进行分析似乎更有感觉.   运行结果:   说明:通过结果我们知道如下信息. 1. 在Java中,中文在用ASCII码表示为3F,实际对应符号'?',用ISO-8859-1表示为3F,实际对应符号也是为'?',这意味着中文已经超出了ASCII和ISO-8859-1的表示范围. 2. UTF

字符,字符集,字符编码

字符,字符集,字符编码 简书郭文圣 现在Unicode已然一统天下,我想很多年轻的程序员可能都没遇到过编码问题,更不用说了解编码的发展了.前些日子在一个老网站上偶遇乱码,虽然入行时间不短,但对其究竟也是不甚了解,好奇心驱使下落入深坑.还好经过一段时间的摸爬滚打,边学边写,总算大概理清了个脉络,记录之,分享之. 概念 字符是一个信息单位,在计算机里面,一个中文汉字是一个字符,一个英文字母是一个字符,一个阿拉伯数字是一个字符,一个标点符号也是一个字符. 字符集是字符组成的集合,通常以二维表的形式存在

关于 MySQL UTF8 编码下生僻字符插入失败/假死问题的分析

1.问题:mysql 遇到某些中文插入异常 最近有同学反馈了这样一个问题: 上述语句在脚本中 load 入库的时候会 hang 住,web 前端.命令行操作则要么抛出 Incorrect string value: '\xF0\xA1\x8B\xBE\xE5\xA2...' for column 'name', 要么存入MYSQL数据库的内容会被截断或者乱码,而换做其它的中文则一切正常. 嗯,看起来有点奇怪哈,按理说 utf8 编码是覆盖了所有中文的,不应该出现上述问题. 2.原因:此 utf8

PHP解码unicode编码的中文字符

问题背景:晚上在抓取某网站数据,结果在数据包中发现了这么一串编码的数据:"......\u65b0\u6d6a\u5fae\u535a......www.jinyuanbao.cn", 这其实是中文被unicode编码后了的数据,我现在就是想解码出中文来,疯狂的google之后,发现很多人贴出了如下的函数,不过我发现根本不好使....如何解码unicode编码的字符?[好使] - PHP网站开发 - [开源与分享]每日最新博客在置顶博客之后: 如何解码unicode编码的字符?[好使]

字符编码介绍及不同编码区别

UNICODE,GBK,UTF-8区别 简单来说,unicode,gbk和大五码就是编码的值,而utf-8,uft-16之类就是这个值的表现形式.而前面那三种编码是一兼容的,同一个汉字,那三个码值是完全不一样的.如"汉"的uncode值与gbk就是不一样的,假设uncode为a040,gbk为b030,而uft-8码,就是把那个值表现的形式.utf-8码完全只针对uncode来组织的,如果GBK要转UTF-8必须先转uncode码,再转utf-8就OK了. 详细的就见下面转的这篇文章. 谈谈Un

字符编码以及python的编码解释

此文转载,供学习之用,看完此文对编码认识也算是大彻大悟啊,纠结了一天的编码,此文乃神作,分享给大家学习 原文链接:http://blog.csdn.net/duqi_yc/article/details/22312983 字符编码 我们已经讲过了,字符串也是一种数据类型,但是,字符串比较特殊的是还有一个编码问题. 因为计算机只能处理数字,如果要处理文本,就必须先把文本转换为数字才能处理.最早的计算机在设计时采用8个比特(bit)作为一个字节(byte),所 以,一个字节能表示的最大的整数就是25

decode 函数将字符串从某种编码转为 unicode 字符

环境:Ubuntu, Python 2.7 基础知识 这个程序涉及到的知识点有几个,在这里列出来,不详细讲,有疑问的直接百度会有一堆的. 1.urllib2 模块的 request 对像来设置 HTTP 请求,包括抓取的 url,和伪装浏览器的代理.然后就是 urlopen 和 read 方法,都很好理解. 2.chardet 模块,用于检测网页的编码.在网页上抓取数据很容易遇到乱码的问题,为了判断网页是 gtk 编码还是 utf-8 ,所以用 chardet 的 detect 函数进行检测.没