在計算機中是以字節爲單位,每一個地址對應一個字節,一個字節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(漢字編碼)