Java中位元組數組與短整數數組的相互轉換
1. 概述
將一種數字類型轉換為另一種類型通常是一個相當簡單的操作。但在某些情況下,由於位元組順序的差異,我們可能會遇到數字表示方面的問題。因此,在處理和轉換過程中,尤其是在接收來自外部的原始資料時,我們應該始終考慮這一點。在本教程中,我們將學習如何將位元組轉換為短整型,反之亦然。我們還將討論這種表示差異的問題和原因,並展示如何在程式碼中解決這個問題。
2. 位元組順序
計算機中的所有數字都以 0 和 1 的組合表示。然而,兩種表示方式之間存在差異。一種稱為大端序,另一種稱為小端序。在大端序中,最高有效位元組位於左側,因此書寫順序是從左到右。而在小端序中,最低有效位元組位於左側,因此順序相反。
造成這種差異的原因是,在某些架構上,小端序可能效能更高,對電腦更友善。同時,大端序更易讀,對人更友善。 大端序是我們通常所說的二進制數表示方法。因此,可能會出現這樣的情況:一個系統產生的資訊與它應該被使用的系統的位元組序不符。
3. Bytes to Shorts
當使用相同位元組順序的數字時,轉換起來非常簡單。我們可以使用多種方法。 例如,我們可以使用循環來解決這種轉換。在這種情況下,我們需要合併兩個位元組;我們只需要使用位移操作稍微移動它們:
public static short[] bytesToShortsBigEndianUsingLoop(byte[] bytes) {
int n = bytes.length / 2;
short[] shorts = new short[n];
for (int i = 0; i < n; i++) {
shorts[i] = (short) (((bytes[2 * i] & 0xFF) << 8) | (bytes[2 * i + 1] & 0xFF));
}
return shorts;
}
請注意,由於這只是一個演示,所以我們這裡沒有處理奇數大小的陣列。在實際生產代碼中,我們應該考慮輸入資料包含奇數字節的情況。我們也可以使用 Stream API 來實現這一點。雖然它並非最完美的解決方案,但在某些情況下,它可能更易於閱讀、更易於實現:
public static short[] bytesToShortsBigEndianUsingStream(byte[] bytes) {
int n = bytes.length / 2;
short[] shorts = new short[n];
IntStream.range(0, n).forEach(i ->
shorts[i] = (short) (((bytes[2 * i] & 0xFF) << 8) | (bytes[2 * i + 1] & 0xFF)));
return shorts;
}
Stream API 方案並不完美,因為它使用了副作用,並且存取了流管道之外的變量,這與函數式程式設計的概念不符。雖然它能正常工作,但存在一些概念和風格上的問題。
如我們所見,將字節序相同的位元組轉換為短整型非常簡單。同樣,當我們需要改變位元組序時,操作也很簡單:
public static short[] bytesToShortsLittleEndianUsingLoop(byte[] bytes) {
int n = bytes.length / 2;
short[] shorts = new short[n];
for (int i = 0; i < n; i++) {
shorts[i] = (short) ((bytes[2 * i] & 0xFF) | ((bytes[2 * i + 1] & 0xFF) << 8));
}
return shorts;
}
邏輯很簡單:我們需要交換字節,讓第一個位元組佔據最低有效位,第二個位元組佔據最高有效位。
4. Shorts to Bytes
反向操作與之非常相似。我們不能使用向下轉型,因為我們的目標是使用單一位元組。因此,我們必須使用掩碼和位移操作來提取位元組:
public static byte[] shortsToBytesBigEndianUsingLoop(short[] shorts) {
byte[] bytes = new byte[shorts.length * 2];
for (int i = 0; i < shorts.length; i++) {
short value = shorts[i];
bytes[2 * i] = (byte) ((value >>> 8) & 0xFF);
bytes[2 * i + 1] = (byte) (value & 0xFF);
}
return bytes;
}
小端序基本上需要相同的程式碼,但我們需要顛倒位元組的寫入順序:
public static byte[] shortsToBytesLittleEndianUsingLoop(short[] shorts) {
byte[] bytes = new byte[shorts.length * 2];
for (int i = 0; i < shorts.length; i++) {
short value = shorts[i];
bytes[2 * i] = (byte) (value & 0xFF);
bytes[2 * i + 1] = (byte) ((value >>> 8) & 0xFF);
}
return bytes;
}
需要注意的是,位元組順序只會影響多位元組值內部的位元組。我們不需要改變位元組的順序,也不需要改變值本身的順序。
5. ByteBuffer
為了簡化流程,我們將使用現有的類別來直接處理轉換。使用ByteBuffer和ByteOrder類,轉換過程如下:
public static short[] bytesToShortsLittleEndian(byte[] bytes) {
short[] shorts = new short[bytes.length / 2];
ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer().get(shorts);
return shorts;
}
這裡我們將位元組封裝在一個緩衝區中,並根據需要更改位元組的解釋方式,而不會改變位元組的實際順序。如方法名稱所示,我們不會複製或修改原始數組,因此任何修改都可能影響結果和處理過程。讀取 short 類型也簡化了操作,因為我們可以傳遞一個空數組來讀取資料。但是,對於奇數大小的數組,我們仍然會遇到同樣的限制。因此,即使使用函式庫方法,我們也應該謹慎處理資料量。
要使用大端字節序,我們可以向 `order` 方法明確指定一個值,也可以省略該值並使用預設值(大端字節序)。預設配置與平台無關,由 JVM 定義。然而,明確配置始終是首選,因為它能提高程式碼的可讀性並有助於避免錯誤。使用ByteBuffer ,以小端字節序將短整數值寫入位元組數組也變得非常簡單:
public static byte[] shortsToBytesLittleEndian(short[] shorts) {
ByteBuffer buffer = ByteBuffer.allocate(shorts.length * 2).order(ByteOrder.LITTLE_ENDIAN);
buffer.asShortBuffer().put(shorts);
return buffer.array();
}
Java 提供了許多方便的類,可以讓我們的程式碼更簡潔, ByteBuffer就是一個很好的例子。雖然我們不會每天都使用它,但了解它的存在很有用。
6. 結論
本文討論了位元組和短整型之間的轉換,並回顧了位元組順序可能帶來的問題。雖然我們可能認為不同平台上的數位表示方式相同,但事實並非如此。字節順序的差異可能會導致一些難以調試的問題。在處理資料時,尤其是在處理來自不同系統的資料時,我們始終需要考慮位元組順序的問題。
不過,Java 中有一些類別可以幫助我們編寫更易讀、更容易理解的程式碼。同時,手動實作轉換邏輯也不太複雜。像往常一樣,本教程中的所有程式碼都可以在 GitHub 上找到。