字節序
我們將一個4字節的漢字存入一段4字節的物理容器裏, 該怎麼存放? 直覺都是從左往右依次寫入, 但也可以從右向左寫, 甚至可以先寫入奇字節再寫偶字節, 這樣比劃下可以有n!種存儲方式(n是字節數), 反正只要保證寫入和讀出的數據一致即可.
這就引入了字節序的問題.
談到字節序的問題,必然牽涉到兩大CPU派系。那就是IBM的PowerPC系列CPU和Intel的x86系列CPU。PowerPC系列採用big endian方式存儲數據,而x86系列則採用little endian方式存儲數據。
那麼究竟什麼是big endian,什麼又是little endian呢?
big endian是指低地址存放最高有效字節(MSB),而little endian則是低地址存放最低有效字節(LSB)。
用文字說明可能比較抽象,下面用二維文字加以說明。比如數字0x12345678在兩種不同字節序CPU中的存儲順序如下所示:
Big Endian:
低地址 高地址
----------------------------------------->
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 12 | 34 | 56 | 78 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Little Endian:
低地址 高地址
----------------------------------------->
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 78 | 56 | 34 | 12 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
從上面兩圖可以看出,採用big endian方式存儲數據是符合我們人類的思維習慣的。而little endian,!@#$%^&*,見鬼去吧 -_-|||
喜歡思考的同學應該想到, 字節序不止big和little endian這2種, 根據排列組合, 總共有n!種(n是字節數), 只是這2種最直接.
爲什麼要注意字節序的問題呢?你可能這麼問。當然,如果你寫的程序只在單機環境下面運行,並且不和別人的程序打交道,那麼你完全可以忽略字節序的存在。但是,如果你的程序要跟別人的程序產生交互呢?
C/C++裏數據存儲順序是跟編譯平臺所在的CPU相關的,而JAVA編寫的程序則唯一採用big endian方式來存儲。試想,如果你用C/C++語言在x86平臺下編寫的程序跟別人的JAVA程序互通時會產生什麼結果?就拿上面的0x12345678來說,你的程序將指向0x12345678的指針傳給了JAVA程序,由於JAVA採取big endian方式存儲數據,很自然的它會將你的數據翻譯爲0x78563412。什麼?竟然變成另外一個數字了?是的,就是這種後果。因此,C程序傳給JAVA程序之前有必要進行字節序的轉換工作。
用通俗的話描述上述的過程就是:
C++: 我這有一段4byte的數據給你用, 內存地址範圍是0x1000到0x1003, 使用愉快:-)
Java: 好嘞. 心想: 嗯...這4個字節找到了, 既然你沒說那我就從左到右讀取吧.
程序員: 得, 完蛋!
網絡傳輸的順序
無獨有偶,所有網絡協議也都是採用big endian的方式來傳輸數據的。所以有時我們也會把big endian方式稱之爲網絡字節序。
當傳輸大文件時候, 比如視頻這種可以"邊下邊播"的內容時, big endian就非常重要了. 當然如果是加密後分段傳輸的數據就無所謂.
當兩臺採用不同字節序的主機通信時,在發送數據之前都必須經過字節序的轉換成爲網絡字節序後再進行傳輸。ANSI C中提供了下面四個轉換字節序的宏。
big endian:最高字節在地址最低位,最低字節在地址最高位,依次排列。
little endian:最低字節在最低位,最高字節在最高位,反序排列。
endian指的是當物理上的最小單元比邏輯上的最小單元小時,邏輯到物理的單元排布關係。咱們接觸到的物理單元最小都是byte,在通信領域中,這裏往往是bit,不過原理也是類似的。
目前應該little endian是主流,因爲在數據類型轉換的時候(尤其是指針轉換)不用考慮地址問題。
Big Endian 和 Little Endian名詞的由來
這兩個古怪的名稱來自英國作家斯威夫特的《格列佛遊記》。在該書中,小人國裏爆發了內戰,戰爭起因是人們爭論,喫雞蛋時究竟是從大頭(Big-endian)敲開還是從小頭(Little-endian)敲開。爲了這件事情,前後爆發了六次戰爭,一個皇帝送了命,另一個皇帝丟了王位。
我們一般將big endian和little endian稱作“大尾”和“小尾”。
在那個時代,Swift是在諷刺英國和法國之間的持續衝突,Danny Cohen,一位網絡協議的早期開創者,第一次使用這兩個術語來指代字節順序,後來這個術語被廣泛接納了
Big Endian 和 Little Endian優劣
Big Endian
判別一個數的正負很容易,只要取offset0處的一個字節就能確認。
Little Endian
長度爲1,2,4字節的數,排列方式都是一樣的,數據類型轉換非常方便。
以上是其他人說的, 其實我覺得吧, 談優劣根本毫無意義...
一些常見文件的字節序
Adobe Photoshop -- Big Endian
BMP (Windows and OS/2 Bitmaps) -- Little Endian
DXF (AutoCad) -- Variable
GIF -- Little Endian
IMG (GEM Raster) -- Big Endian
JPEG -- Big Endian
FLI (Autodesk Animator) -- Little Endian
MacPaint -- Big Endian
PCX (PC Paintbrush) -- Little Endian
PostScript -- Not Applicable (text!)
Microsoft RIFF (.WAV & .AVI) -- Both
Microsoft RTF (Rich Text Format) -- Little Endian
SGI (Silicon Graphics) -- Big Endian
Sun Raster -- Big Endian
TGA (Targa) -- Little Endian
TIFF -- Both, Endian identifier encoded into file
BSON -- Little Endian
比特序
可是有朋友仍然會問,CPU存儲一個字節的數據時其字節內的8個比特之間的順序是否也有big endian和little endian之分?或者說是否有比特序的不同?
實際上,這個比特序是同樣存在的。下面以數字0xB4(10110100)加以說明。
Big Endian
msb lsb
---------------------------------------------->
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 1 | 0 | 1 | 1 | 0 | 1 | 0 | 0 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Little Endian
lsb msb
---------------------------------------------->
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 0 | 0 | 1 | 0 | 1 | 1 | 0 | 1 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
實際上,由於CPU存儲數據操作的最小單位是一個字節,其內部的比特序是什麼樣對我們的程序來說是一個黑盒子。也就是說,你給我一個指向0xB4這個數的指針,對於big endian方式的CPU來說,它是從左往右依次讀取這個數的8個比特;而對於little endian方式的CPU來說,則正好相反,是從右往左依次讀取這個數的8個比特。而我們的程序通過這個指針訪問後得到的數就是0xB4,字節內部的比特序對於程序來說是不可見的,其實這點對於單機上的字節序來說也是一樣的。
那可能有人又會問,如果是網絡傳輸呢?會不會出問題?是不是也要通過什麼函數轉換一下比特序?嗯,這個問題提得很好。假設little endian方式的CPU要傳給big endian方式CPU一個字節的話,其本身在傳輸之前會在本地就讀出這個8比特的數,然後再按照網絡字節序的順序來傳輸這8個比特,這樣的話到了接收端不會出現任何問題。
其實網絡傳輸的字節序也好, 比特序也好, 都由網絡協議來定義, 比如二進制的http/2, 還有bson, 他們的每一個字段之前有記錄着字段長度,至於字段的序, 像bson採用的就是little endian.
一維信息存在的條件: 順序性
頭腦風暴: 字節序問題的根源在哪裏?
任何信息都可以用一個數值來表示, 無論多少進制, 信息的每一個位必須從左向右依次排列纔有意義. 比如一個數0x1234如果把其中2個字節位置調換就變成了0x3412: 這樣信息就失效了.
好, 由於計算機處理數據最小單位是1個8位的字節, 可以想象任何數據都是一個2^8=256進制的數值, 一個n字節的數據就是一個n位256進制數.
描述一段n=3的信息的地址就需要依次給出從左到右每個字節的地址, 也就是這個256進制數的"百位", "十位", "個位". 假如這3個字節的內存地址分別是0,3和2, 那這個數據的地址可以用一個數組表示: [0,3,2], 但是通常數據在存儲空間裏是連續的, 比如是[5,6,7], 這時候可以通過另一種寫法節省空間:{start: 5, end: 8}, 或者{start:5, length:3}. 這樣子存儲地址的成本從原來的n個降低成2個.
當然, 理論上如果讓所有的數據等長, 地址成本就是1個, 不過這個不現實.
之所以上面的地址成本可以降低至2個, 是因爲默認了字節順序是從左至右(big endian), 而且中間沒有斷點. 當然這個默認順序也可以是little endian, 甚至是其他的序, 由此引發了本篇文章的核心問題.
順序問題一直以默認的方式存在, 比如文本的排列總是默認從左到右, 因爲字符串中每個字符的信息只表示自己是哪個字符, 並沒有透露自己和其他字符之間的位置關係, 所以文本渲染引擎都是從左向右渲染的, 當然也有RTL(right 2 left)的存在, RTL就像little endian一樣不可理喻, 哦, RTL至少是UI層面的, 還有一定的存在意義, little endian就........
什麼玩意?