老天爺你算是贏了,忽冷忽熱的搞毛啊。最近因為各種原因掌握了豐富的就診經驗(好吧,和這篇博文一樣毫無意義的技能Get★daze)。
本來這篇博文的內容是上周沖澡的時候腦洞大開的成果,結果由於各(lan)種(de)原(dong)因(shou)拖延到了今天,那就順手當作三八婦女節獻禮好了(<ゝω・)綺羅星☆。
今天要討論的是有多少種無趣的手段把一個UInt32拆成4個Byte。至於其他長度整形的拆解就請自行舉一反三吧。
首先,肯定是第一個想到的,就是進行位運算。沒什么好說的,關門放代碼:
byte p1=(byte)(ipt >> 24); byte p2=(byte)(ipt >> 16); byte p3=(byte)(ipt >> 8); byte p4=(byte)(ipt);
上述代碼通過位移分別從高到低取得4個byte,簡單實用。通常這種方法就完全夠用了(唯一的問題就是需要口算位移……好煩)。但是標題都劇透了1、2、3,這時候放棄博文就結束了。那么,乘我還沒被凍死就進入方法2吧。
方法2的想法倒是很直接:既然一個UInt32在內存中分配了4個字節的長度,為何我們不直接訪問這4個字節呢?就像吐司面包一樣,直接平分4片就是了。
為了平分一個UInt32,就要直接操作內存。C#中除了幾個特殊的地方,平常沒有人需要用到指針這玩意。不過既然玩的就是腦洞打開,還是果斷祭出許久沒見到的指針吧:
byte* p = (byte*)&ipt; byte p1=p[3]; byte p2=p[2]; byte p3=p[1]; byte p4=p[0];
由於X86平台上采用的是Little Ending布局,所以這里內存訪問的順序是和直覺相反的(好吧,其實內存布局問題早忘了,要不是看到結果反了也不會順手查了下WIKI)。小細節不要那么在意啦,還是速度說下下一種吧。
方法3其實應該算是方法2的變種。既然用指針能訪問每個byte,那除了指針外還有什么方法也能達到同樣效果呢?
C里面有個叫共用體(union)的特殊類型,此類型的神奇效果是其字段共用一個內存位置,也就是說一個UInt32在內存中是可以和4個Byte重疊在一起的。如果我們可以用這種方法的話,不就意味着可以直接讀到分割的結果了么?那么,唯一的問題是共用體在C#中沒見過啊?沒有一個類型叫union的玩意?
其實,這玩意是存在的。只是除了P/Invoke調用以外實在太無存在干了。其實就是通過StructLayoutAttribute指定手動內存布局,並通過FieldOffsetAttribute手動將內存布局重疊到一起。代碼如下:
[StructLayout(LayoutKind.Explicit)] internal struct UIntByteConverter { [FieldOffset(0)] public uint Source; [FieldOffset(0)] public byte part4; [FieldOffset(1)] public byte part3; [FieldOffset(2)] public byte part2; [FieldOffset(3)] public byte part1; }
然后接下來的事情就簡單了,也就是對Source賦值完直接讀取結果罷了。
那么,本篇博文就這么結束了。有沒有覺得什么東西怪怪的?
有么?
沒有么?
有么?
這是湊字數么?
這不是湊字數么?
……
……
……
……
好吧,其實從標題開始我就沒說過只有三種方法嘛。至少還有種方法我是見別人有這么用過(不過對一個不太水的程序員來說,應該是毫無意義的方法來着)。
方法0(對,我就是喜歡從零數起,你咬我啊):轉成字符串然后拆分字符串再轉回來。
一看就知道效率很低,而且其實寫起來也很麻煩……
var temp=ipt.ToString("X8"); byte p1=byte.Parse(temp.Substring(0, 2), NumberStyles.HexNumber); byte p2=byte.Parse(temp.Substring(2, 2), NumberStyles.HexNumber); byte p3=byte.Parse(temp.Substring(4, 2), NumberStyles.HexNumber); byte p4=byte.Parse(temp.Substring(6, 2), NumberStyles.HexNumber);
那么,以上方法的效率如何呢?說實話除了方法3要多一個對象創建而方法0完全坑爹外差別好小。對一個沒停葯的人來說方法1完全夠了(不過我總覺得某些方面方法3也有奇效)。具體如何請自行下載代碼自行跑測試吧。