字节的游戏
業(yè)務(wù)處理上,有時(shí)會(huì)直接對(duì)字節(jié)進(jìn)行操作。例如實(shí)現(xiàn)私有協(xié)議,對(duì)校驗(yàn)位進(jìn)行檢測(cè),敏感數(shù)據(jù)加密等。博主查了 一下網(wǎng)上的資料,發(fā)現(xiàn)有不少都是錯(cuò)誤的。甚至連《Thinking in Java》的解釋都很令人困惑,以下是從書(shū)中摘錄的原文:
如果對(duì)char、byte或者short類型的數(shù)值驚醒移位處理,那么在移位之前,他們會(huì)被轉(zhuǎn)換為int類型,并且得到的結(jié)果也是一個(gè)int類型。只有數(shù)值右端的低5位才有用。
當(dāng)時(shí)讀到這一句的時(shí)候,我理解了很久,至今沒(méi)有明白“只有數(shù)值右端的低5位才有用”的含義。理解字節(jié)處理的基本方法就是動(dòng)手操作,下面我會(huì)結(jié)合用例進(jìn)行解釋。
首先,我們需要理解幾個(gè)基礎(chǔ)概念。一般來(lái)說(shuō),字節(jié)是我們可以用語(yǔ)言處理的最小對(duì)象,無(wú)論是C/C++還是Java都沒(méi)有直接提供bit類型。1 byte = 8 bit,除去最左側(cè)的符號(hào)位1byte可以描述的范圍是:-128 ~ 127。但是在大多數(shù)的業(yè)務(wù)處理中,我們通常會(huì)采用無(wú)符號(hào)位,即用1byte表示:0 ~ 255。其次,常見(jiàn)的移位操作符有左移(<<) 和右移 (>>),比較容易忽視的是右移操作,如果最左側(cè)的符號(hào)位為1則右移是在高位插入的是——1。因此Java中增加了一種“無(wú)符號(hào)”右位移操作符>>>,通常用不上了解即可。最后,如果我們采用byte[]來(lái)表示一種數(shù)據(jù)類型,數(shù)組下標(biāo)從小到大即內(nèi)存地址的從低位到高位。記住這個(gè)概念非常重要,后面我會(huì)引入大端模式與小端模式。
為了讓大家理解以上概念,下面看兩個(gè)例子:
1. 假設(shè)byte x = 127,對(duì)它執(zhí)行左移1位的操作 x = ?
byte x = 127; x <<= 1; System.out.println(Integer.toHexString(x));在代碼執(zhí)行之前我們先使用計(jì)算器計(jì)算一下:BIN(1111 1110) HEX(FE),代碼的執(zhí)行結(jié)果為:FFFFFFFE。原因是對(duì)x左移1位超出了byte的表示范圍,Java自動(dòng)在左側(cè)補(bǔ)位,由于最高位是1,因此我們獲得了一個(gè)怪異的結(jié)果。那么有什么辦法得到一個(gè)正確的結(jié)果呢?
byte x = 127; x <<= 1; System.out.println(Integer.toHexString(x & 0xFF));2. 假設(shè)byte x = 1,對(duì)它執(zhí)行左移32位的操作 x = ?
byte x = 1; System.out.println(x << 32);答案是1。這個(gè)結(jié)論比較怪異而且確實(shí)是一個(gè)坑,大家只需要記住:對(duì)一個(gè)int值來(lái)說(shuō),左移32位等于它的原始值;對(duì)于一個(gè)long值來(lái)說(shuō),左移64位等于它的原始值。
在理解了這些基本概念以后,我們已經(jīng)做好了進(jìn)入字節(jié)世界的準(zhǔn)備。
我們?nèi)绾斡?個(gè)字節(jié)的大端模式表示一個(gè)整型變量?
對(duì)大端模式的定義為:數(shù)據(jù)的高字節(jié)保存在內(nèi)存的低地址中,而數(shù)據(jù)的低字節(jié)保存在內(nèi)存的高地址中。這個(gè)說(shuō)法很繞而且也不利于理解,對(duì)于數(shù)字常量來(lái)說(shuō)0x1234,1即為高位,4即為低位。而對(duì)于byte[4]來(lái)說(shuō),bs[0]即為地址低位,bs[3]即為地址高位。這樣看來(lái)就很清楚了。大端模式符合人們的閱讀模式。
int i = 0x1234; byte[] bs = new byte[4]; bs[3] = (byte) (i & 0xFF); bs[2] = (byte) (i >> 8 & 0xFF); bs[1] = (byte) (i >> 16 & 0xFF); bs[0] = (byte) (i >> 24 & 0xFF);for(byte b : bs) {System.out.println(Integer.toHexString(b)); }更抽象的算法,大家可以在理解了上面的例子以后自己封裝。
反過(guò)來(lái)我們將以大端模式生成的4個(gè)字節(jié)還原為一個(gè)整型數(shù)?
int x = bs[3] & 0xFF; x |= bs[2] & 0xFF << 8; x |= bs[1] & 0xFF << 16; x |= bs[0] & 0xFF << 24;System.out.println(Integer.toHexString(x));注意:為了得到正確的結(jié)果,我們?cè)趯?duì)byte進(jìn)行移位前一定要先做位與(&)操作。
接下來(lái)我們需要升級(jí)問(wèn)題,將一個(gè)8個(gè)字節(jié)寬度的符合大端模式的字節(jié)數(shù)組還原為一個(gè)長(zhǎng)整型數(shù)。
long x = bs[7] & 0xFF; x |= (bs[6] & 0xFF) << 8; x |= (bs[5] & 0xFF) << 16; x |= (bs[4] & 0xFF) << 24; x |= (bs[3] & 0xFF) << 32; x |= (bs[2] & 0xFF) << 40; x |= (bs[1] & 0xFF) << 48; x |= (bs[0] & 0xFF) << 56; System.out.println(Long.toHexString(x));?
似乎我們很容易按照整型的轉(zhuǎn)換方式得到以上算法。不幸的是,這樣做是錯(cuò)誤的。如果這個(gè)byte[]表示的數(shù)字范圍超過(guò)整型數(shù)的上限,我們將無(wú)法獲得正確的長(zhǎng)整型數(shù)。原因是Java默認(rèn)在對(duì)byte進(jìn)行移位操作前會(huì)轉(zhuǎn)換為int類型,還記得上面我們讓大家記住“對(duì)一個(gè)int值來(lái)說(shuō),左移32位等于它的原始值”嗎?正確的做法應(yīng)該是這樣:
long x = bs[7] & 0xFF; x |= ((long)bs[6] & 0xFF) << 8; x |= ((long)bs[5] & 0xFF) << 16; x |= ((long)bs[4] & 0xFF) << 24; x |= ((long)bs[3] & 0xFF) << 32; x |= ((long)bs[2] & 0xFF) << 40; x |= ((long)bs[1] & 0xFF) << 48; x |= ((long)bs[0] & 0xFF) << 56; System.out.println(Long.toHexString(x));?
至此我們應(yīng)該可以很輕松的解決有關(guān)字節(jié)轉(zhuǎn)換的各種難題了,但是上面的這些算法未免顯得太不優(yōu)美,幸虧Java早就為我們想到了這一點(diǎn)。本著不要重復(fù)造輪子的觀點(diǎn),我提供了一套工具。
/*** 任意字節(jié)寬度轉(zhuǎn)換為標(biāo)準(zhǔn)整型數(shù)*/ public static int bytesToInt(byte[] bytes, int byteNum, ByteOrder order) {ByteBuffer buffer = ByteBuffer.allocate(4);buffer.order(order);buffer.put(bytes, 0, bytes.length);buffer.put(new byte[buffer.limit() - byteNum], 0, buffer.limit() - byteNum);buffer.flip();return buffer.getInt(); }/*** 長(zhǎng)整型數(shù)轉(zhuǎn)換為指定字節(jié)寬度*/ public static byte[] longToBytes(long x, int byteNum, ByteOrder order) {ByteBuffer buffer = ByteBuffer.allocate(8);buffer.order(order);buffer.putLong(0, x);return Arrays.copyOfRange(buffer.array(), 0, byteNum); }/*** 任意字節(jié)寬度轉(zhuǎn)換為長(zhǎng)整型*/ public static long bytesToLong(byte[] bytes, int byteNum, ByteOrder order) {ByteBuffer buffer = ByteBuffer.allocate(8);buffer.order(order);buffer.put(bytes, 0, bytes.length);buffer.put(new byte[buffer.limit() - byteNum], 0, buffer.limit() - byteNum);buffer.flip();return buffer.getLong(); }/*** 長(zhǎng)整型數(shù)轉(zhuǎn)換為標(biāo)準(zhǔn)的8字節(jié)寬度*/ public static byte[] longToBytes(long x, ByteOrder order) {ByteBuffer buffer = ByteBuffer.allocate(8);buffer.order(order);buffer.putLong(0, x);return buffer.array(); }/*** 標(biāo)準(zhǔn)8字節(jié)寬度轉(zhuǎn)換為長(zhǎng)整型數(shù)*/ public static long bytesToLong(byte[] bytes, ByteOrder order) {ByteBuffer buffer = ByteBuffer.allocate(8);buffer.order(order);buffer.put(bytes, 0, bytes.length);buffer.flip();return buffer.getLong(); }/*** 整型數(shù)轉(zhuǎn)換為標(biāo)準(zhǔn)4字節(jié)寬度*/ public static byte[] intToBytes(int x, ByteOrder order) {ByteBuffer buffer = ByteBuffer.allocate(4);buffer.order(order);buffer.putInt(0, x);return buffer.array(); }/*** 標(biāo)準(zhǔn)4字節(jié)寬度轉(zhuǎn)換為整型數(shù)*/ public static int bytesToInt(byte[] bytes, ByteOrder order) {ByteBuffer buffer = ByteBuffer.allocate(4);buffer.order(order);buffer.put(bytes, 0, bytes.length);buffer.flip();return buffer.getInt(); }轉(zhuǎn)載于:https://www.cnblogs.com/learnhow/p/10800153.html
總結(jié)
- 上一篇: 分辨率与栅格系统的对应关系:
- 下一篇: 生物-脑-脑容量:脑容量