字節的遊戲

業務處理上,有時會直接對字節進行操做。例如實現私有協議,對校驗位進行檢測,敏感數據加密等。博主查了 一下網上的資料,發現有很多都是錯誤的。甚至連《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[]來表示一種數據類型,數組下標從小到大即內存地址的從低位到高位。記住這個概念很是重要,後面我會引入大端模式與小端模式。加密

爲了讓你們理解以上概念,下面看兩個例子:spa

1. 假設byte x = 127,對它執行左移1位的操做 x = ?code

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 = ?blog

byte x = 1;
System.out.println(x << 32);

答案是1。這個結論比較怪異並且確實是一個坑,你們只須要記住:對一個int值來講,左移32位等於它的原始值;對於一個long值來講,左移64位等於它的原始值。ip

在理解了這些基本概念之後,咱們已經作好了進入字節世界的準備。內存

咱們如何用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();
}
相關文章
相關標籤/搜索