老天爷你算是赢了,忽冷忽热的搞毛啊。最近因为各种原因掌握了丰富的就诊经验(好吧,和这篇博文一样毫无意义的技能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也有奇效)。具体如何请自行下载代码自行跑测试吧。