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