在计算机中是以字节为单位,每一个地址对应一个字节,一个字节8bit。在C中,除了8bit的char之外,还有16bit的short,32位的int,64位long,固然具体要由编译器决定,能够经过sizeof来获取不一样类型在内存中占用的字节数。在计算机系统中,当物理单位的长度大于1个字节时,就要区分字节顺序。常见的字节顺序有两种:Big Endian(High-byte first)和Litter Endian(Low-byte first),固然还有其余字节顺序,但不常见,例如Middle Endian。html
1、最高有效位、最低有效位c#
要理解Big Endian和Little Endian,首先要搞清楚MSB和LSB。windows
一、MSB(Most Significant Bit)最高有效位浏览器
在一个n位二进制数字中n-1位,也就是最左边的位。网络
二、LSB(Least Significant Bit)最低有效位架构
指最右边的位。函数
例如:一个int类型的整型123456789ui
二进制表达方式:0000 0111 0101 1011 1100 1101 0001 0101(从右向左,每4bit对齐,最左边(高位)不够用0补齐)this
十六进制表达方式:0 7 5 B C D 1 5编码
按照上述关于MSB和LSB的意思,在二进制表达方式中,bit从0开始,从右向左,bit0为最低有效位,而bit23为最高有效位。而咱们通常称左边的0x07为高位字节,0x15为低位字节。
再通俗一点解释就是:8421码的,8这端为高位,1这端为低位,相应的字节则分别称为高位字节和低位字节。
2、内存地址
在内存中,多字节对象都是被存储为连续的字节序列。例如在C语言中,一个类型为int的变量n,若是其存储的首个字节的地址为0x1000,那么剩余3个字节的地址将存储在0x1001~0x1003。总之,无论具体字节顺序是以什么方式排列,内存地址的分配通常是从小到大的增加。咱们常把0x1000称为低地址端,把0x1003称为高地址端。
3、大端和小端
搞清楚MSB、LSB、高位字节、低位字节、内存地址以后,再理解大端和小端,就至关容易了,先看看概念:
小端Little Endian:低字节存放在低地址,低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。
大端Big Endian:高字节存放在低地址,即高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。
以二节中的例子int类型整数123456789为例:
小端在内存中排列:0x15 0xCD 0x5B 0x07 (低位在前)
大端在内存中排列:0x07 0x5B 0xCD 0x15 (高位在前)
从例子中能够看出小端比较符合人的思惟,而大端则看上去很是直观。
注:
一、例子中是假设编译器支持int为32位的前提下,若是是16位,那大端的排列则为:0xCD 0x15 0x07 0x5B。
二、大小端通常是由CPU架构决定,常见的Intel、AMD的CPU使用的是小端字节序,而PowerPC使用的是大端字节序,有些ARM处理器还能够选择用大端仍是小端模式,具体请自行查阅。
三、c#中,字节序跟编译平台所在的CPU相关,例如在Intel x86 CPU架构的windows平台中,c#采用的小端序。而Java因为其JVM屏蔽了不一样CPU架构致使的字节序差别,因此默认采用大端字节序。因此,大小端模式是由CPU决定,而编译器又可能会改变这种模式。
字节序 | 内存地址 | int(16bit) | int(32bit) | 特色 |
小端 | 0x1001,0x1002,0x1003,0x1004 | 0x15 0xCD 0x5B 0x07 | 0x15 0xCD 0x5B 0x07 | 低地址端存储低位字节,低位在前 |
大端 | 0x1001,0x1002,0x1003,0x1004 | 0xCD 0x15 0x07 0x5B | 0x07 0x5B 0xCD 0x15 | 低地址端存储高位字节,高位在前 |
4、网络字节序和主机字节序
网络字节序(Network Order):TCP/IP各层协议将字节序定义为Big Endian,所以TCP/IP协议中使用的字节序一般称之为网络字节序。
主机字节序(Host Order):整数在内存中保存的顺序,它遵循Little Endian规则(不必定,要看主机的CPU架构)。因此当两台主机之间要经过TCP/IP协议进行通讯的时候就须要调用相应的函数进行主机序列(Little Endian)和网络序(Big Endian)的转换。
若是是作跨平台开发时,双方须要协商好字节序,而后根据程序运行的环境,肯定是否须要字节序转换。
例如约定的通信字节序位Big Endian,默认的windows采用的Little Endian,那收到数据后就须要作转换操做。
5、C#位操做符
这里简单记录一下C#的位操做符,方便之后本身查阅,也方便理解后面的讲解。
一、按位与&
1&0为0;0&0为0;1&1为1。
二、按位或|
1|0为1;0|0为0;1|1为1。
三、按位取反~
~1为0;~0为1。
四、按位异或^
1^1为0;0^0为0;1^0为1。相等得0,相异等1。
五、左移<<
位左移运算,将整个数向左移若干位,左移后空出的部分用0补齐。
六、右移>>
位右移运算,将整个数向右移若干位,右移后空出的部分用0补齐。
6、C#中关于大端和小端的转换
一、重复轮子
using System; namespace Framework.NetPackage.Common { /// <summary> /// 字节序转换辅助类 /// </summary> public static class Endian { public static short SwapInt16(this short n) { return (short)(((n & 0xff) << 8) | ((n >> 8) & 0xff)); } public static ushort SwapUInt16(this ushort n) { return (ushort)(((n & 0xff) << 8) | ((n >> 8) & 0xff)); } public static int SwapInt32(this int n) { return (int)(((SwapInt16((short)n) & 0xffff) << 0x10) | (SwapInt16((short)(n >> 0x10)) & 0xffff)); } public static uint SwapUInt32(this uint n) { return (uint)(((SwapUInt16((ushort)n) & 0xffff) << 0x10) | (SwapUInt16((ushort)(n >> 0x10)) & 0xffff)); } public static long SwapInt64(this long n) { return (long)(((SwapInt32((int)n) & 0xffffffffL) << 0x20) | (SwapInt32((int)(n >> 0x20)) & 0xffffffffL)); } public static ulong SwapUInt64(this ulong n) { return (ulong)(((SwapUInt32((uint)n) & 0xffffffffL) << 0x20) | (SwapUInt32((uint)(n >> 0x20)) & 0xffffffffL)); } } }
二、BCL库支持的函数
System.Net.IPAddress.HostToNetworkOrder、System.Net.IPAddress.NetworkToHostOrder,这两个函数的内部实现和上面重复轮子原理如出一辙。
7、关于负数
在计算机中,负数以其绝对值的补码形式表示,不明白能够查阅九中贴出的相关资源。关于负数的字节序跟通常整数的字节序处理没有任何区别。
8、关于汉字编码以及与字节序的关系
一、对于gb23十二、gbk、gb18030、big5,其编码某个汉字产生的字节顺序,由其编码方案自己决定,不受CPU字节序的影响。其实这几种编码的字节序和大端模式的顺序是一致的。
在使用GB2312的程序一般采用EUC储存方法,以便兼容于ASCII。浏览器编码表上的“GB2312”,一般都是指“EUC-CN”表示法。
每一个汉字及符号以两个字节来表示。第一个字节称为“高位字节”,第二个字节称为“低位字节”。
“高位字节”使用了0xA1-0xF7(把01-87区的区号加上0xA0),“低位字节”使用了0xA1-0xFE(把01-94加上0xA0)。因为一级汉字从16区起始,汉字区的“高位字节”的范围是0xB0-0xF7,“低位字节”的范围是0xA1-0xFE,占用的码位是72*94=6768。其中有5个空位是D7FA-D7FE。
例如“啊”字在大多数程序中,会以两个字节,0xB0(第一个字节)0xA1(第二个字节)储存。(与区位码对比:0xB0=0xA0+16,0xA1=0xA0+1)。
二、UTF-8
UTF-8和gb系列编码同样,其编码某个汉字产生的字节顺序,由其编码方案决定,不受CPU字节序的影响。不管一个汉字有多少个字节,它的字节序与编码顺序保持一致。
例如汉字”严”利用utf8编码过程:
一、已知“严”的unicode编码是4E25(100111000100101),根据utf8规则能够得知其utf8编码须要三个字节。
即格式是“1110xxxx 10xxxxxx 10xxxxxx”
第一个字节前三位表示了字符“严”被编码成utf8后的编码长度,有多长,则从左开始填多少个1,若是只有1个字节,则第一个位为0。
对于编码后大于1个字节的符号,第一个字节的第四位为0,其余字节前两位均要求为10。
二、从”严“的最后一个二进制位开始,依次从后向前填入格式中的x,多出的位补0。这样就获得了“严”的utf8编码为“11100100 10111000 10100101”,转换成十六进制就是E4B8A5。
编码示例过程参考的原文:http://www.ruanyifeng.com/blog/2007/10/ascii_unicode_and_utf-8.html
从上述过程能够看到,utf8的字节序已经由其编码方案决定,不受CPU字节序影响。
三、Unicode
Unicode只是一个符号集,它只规定了符号的二进制代码,却没有规定这个二进制代码应该如何存储。因此他没有要求如何存储编码后的字节,也就受CPU字节序的影响。
Unicode的具体实现包括UTF-1六、UTF-32(固然也包括UTF-8,但因为其编码方式和编码后的字节序与其余Unicode编码实现有较大区别,因此单独拿出来说解的)。
四、总结
一、网络通信
在实际的网络通信中,网络协议例如TCP是规定网络字节序(Network Order)是大端。而针对汉字具体使用什么编码,通信双方要么提早约定好,要么就须要在数据包中标识好汉字具体使用的编码。
若是在网络通信中,涉及例如UTF16这样区分大小端的编码,除非按网络协议要求采用大端模式是,不然也要事先约定好,或者在数据包中标识好使用的字节序模式。
二、文件
文件的也会存储汉字,固然也要进行编码。若是采用UTF-16这样的有字节序模式区分的编码,编码规则要求能够在文件头部的BOM(Byte Order Mark)来标记。若是没有标记,除非事先知道字节序的模式,不然只能大小端都试一遍。
Unicode规范中推荐的标记字节顺序的方法是BOM。BOM不是“Bill Of Material”的BOM表,而是Byte Order Mark。BOM是一个有点小聪明的想法:
在Unicode编码中有一个叫作”ZERO WIDTH NO-BREAK SPACE”的字符,它的编码是FEFF。而FEFF在Unicode中是不存在的字符,因此不该该出如今实际传输中。UCS(Unicode的学名)规范建议咱们在传输字节流前,先传输字符“ZERO WIDTH NO-BREAK SPACE”。
这样若是接收者收到FEFF,就代表这个字节流是Big-Endian的;若是收到FFFE,就代表这个字节流是Little-Endian的。所以字符“ZERO WIDTH NO-BREAK SPACE”又被称做BOM。
UTF-8不须要BOM来代表字节顺序,但能够用BOM来代表编码方式。字符“ZERO WIDTH NO-BREAK SPACE”的UTF-8编码是EF BB BF。因此若是接收者收到以EF BB BF开头的字节流,就知道这是UTF-8编码了。
9、参考资源
http://baike.baidu.com/view/1922338.htm
http://www.cppblog.com/izualzhy/archive/2011/10/20/158784.html
http://www.cnblogs.com/junsky/archive/2009/08/06/1540727.html(负数的二进制表示方法)
http://www.cnblogs.com/augellis/archive/2009/09/29/1576501.html (sizeof)
http://www.ruanyifeng.com/blog/2007/10/ascii_unicode_and_utf-8.html(汉字编码)
http://djt.qq.com/article/view/658?ADTAG=email.InnerAD.weekly.20130902(汉字编码)