Java 設計之初為簡化語言、減少開發者認知負擔,故意移除 C/C++ 的 unsigned 關鍵字。實際開發中(網絡協議、文件格式、硬件交互、數據庫遷移)仍需處理無符號數值。本文系統梳理 **擴容類型轉換、位運算技巧、BigInteger 大數處理、序列化/反序列化最佳實踐**,附代碼模板與性能對比。
James Gosling 曾在訪談中解釋:
「Quiz any C developer about unsigned, and pretty soon you discover that almost no C developers actually understand what goes on with unsigned, what unsigned arithmetic is. Things like that made C complex. The language part of Java is, I think, pretty simple.」
核心考量:
long (64bit) 覆蓋 unsigned int (32bit) 範圍,int (32bit) 覆蓋 unsigned short (16bit),short (16bit) 覆蓋 unsigned byte (8bit)| C/C++ 無符號類型 | 位寬 | 範圍 | Java 對應類型 | 處理方式 |
|---|---|---|---|---|
unsigned char / uint8_t | 8 | 0 ~ 255 | short (16bit) 或 int | 推薦 int,避免 short 符號擴展陷阱 |
unsigned short / uint16_t | 16 | 0 ~ 65,535 | int (32bit) | 直接賦值,無溢出風險 |
unsigned int / uint32_t | 32 | 0 ~ 4,294,967,295 | long (64bit) | 必須用 long,int 會負數溢出 |
unsigned long / uint64_t | 64 | 0 ~ 2^64-1 | BigInteger | Java long 為有符號 64bit,無法容納完整範圍 |
byte b = (byte) 0xFF; // -1 (有符號)
int i1 = b; // -1 (符號擴展:0xFFFFFFFF)
int i2 = b & 0xFF; // 255 (正確!掩碼保留低 8 位)
鐵律:將無符號 byte/short 轉 int/long 時,**必須先與掩碼 & 0xFF / & 0xFFFF 運算**,再賦值給寬類型。
public final class UnsignedUtils {
// 1. 無符號 byte → int (0~255)
public static int toInt(byte b) { return b & 0xFF; }
// 2. 無符號 short → int (0~65535)
public static int toInt(short s) { return s & 0xFFFF; }
// 3. 無符號 int → long (0~4294967295)
public static long toLong(int i) { return i & 0xFFFFFFFFL; }
// 4. long → 無符號 int 存儲 (截取低 32 位)
public static int fromLong(long l) { return (int) (l & 0xFFFFFFFFL); }
// 5. BigInteger ←→ 無符號 long (0~2^64-1)
public static BigInteger toBigInteger(long unsignedLong) {
return BigInteger.valueOf(unsignedLong & 0x7FFFFFFFFFFFFFFFL)
.setBit(63); // 處理符號位,實際建議用 new BigInteger(1, toBytes(unsignedLong))
}
public static byte[] toBytes(long unsignedLong) {
ByteBuffer buf = ByteBuffer.allocate(8).order(ByteOrder.BIG_ENDIAN);
buf.putLong(unsignedLong);
return buf.array();
}
public static long fromBytes(byte[] bytes) {
ByteBuffer buf = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);
return buf.getLong(); // 返回有符號 long,邏輯值視為無符號
}
// 6. 無符號右移 >>> (Java 獨有)
public static int unsignedRightShift(int val, int shift) {
return val >>> shift; // 高位補 0,而非符號位
}
// 7. 比較兩個「無符號 int」大小
public static int compareUnsigned(int a, int b) {
return Long.compare(a & 0xFFFFFFFFL, b & 0xFFFFFFFFL);
}
// 8. 格式化輸出無符號值
public static String toUnsignedString(int val) {
return Long.toUnsignedString(val & 0xFFFFFFFFL);
}
public static String toUnsignedString(long val) {
return Long.toUnsignedString(val);
}
// 9. 解析無符號字符串
public static long parseUnsignedLong(String s) {
return Long.parseUnsignedLong(s);
}
public static int parseUnsignedInt(String s) {
return (int) Long.parseUnsignedLong(s);
}
}
JDK 8 新增 Integer.toUnsignedString()、Long.parseUnsignedLong()、Integer.compareUnsigned()、Integer.divideUnsigned()、Integer.remainderUnsigned() 等靜態方法,**優先使用標準庫**,避免重複造輪子。
// 將 4 byte 無符號數組轉為長整型
public static long ipv4ToLong(byte[] bytes) {
return ((bytes[0] & 0xFFL) << 24) |
((bytes[1] & 0xFFL) << 16) |
((bytes[2] & 0xFFL) << 8) |
(bytes[3] & 0xFFL);
}
// 反向
public static byte[] longToIpv4(long ip) {
return new byte[] {
(byte) (ip >> 24),
(byte) (ip >> 16),
(byte) (ip >> 8),
(byte) ip
};
}
// 讀取 4 字節無符號大端整數
public static long readUInt32BE(InputStream in) throws IOException {
byte[] buf = new byte[4];
in.read(buf);
return ((buf[0] & 0xFFL) << 24) |
((buf[1] & 0xFFL) << 16) |
((buf[2] & 0xFFL) << 8) |
(buf[3] & 0xFFL);
}
// MySQL UNSIGNED BIGINT 範圍 0 ~ 18446744073709551615
// Java 長整型範圍 -9223372036854775808 ~ 9223372036854775807
// 方案:讀取為 BigInteger,或存儲為 Decimal(20,0)
@Converter
public class UnsignedLongConverter implements AttributeConverter {
@Override public BigInteger convertToDatabaseColumn(Long attr) {
return attr == null ? null : BigInteger.valueOf(attr & 0xFFFFFFFFFFFFFFFFL);
}
@Override public Long convertToEntityAttribute(BigInteger db) {
return db == null ? null : db.longValue(); // 邏輯值視為無符號
}
}
| 操作 | long (基本類型) | BigInteger | 建議 |
|---|---|---|---|
| 加減乘除 | ~1 CPU 週期 | ~50-200 ns (對象分配、方法調用) | 熱路徑用 long + 掩碼 |
| 比較 | ~1 週期 | ~30 ns | 用 Long.compareUnsigned() |
| 內存 | 8 字節 (棧上) | ~32 字節 + 堆對象頭 | 大量數組用 long[] |
| 序列化 | 極快 | 需轉 byte[] | Protocol Buffers / Avro 原生支持 |
byte→int、short→int、int→long,配合 & 掩碼 修正符號擴展Long.toUnsignedString()、Long.parseUnsignedLong()uint32/uint64、Avro long (logicalType: unsigned-long)、Thrift i64 + 注解_u32、_u64,Javadoc 標註「無符號值」Java 無 unsigned 非缺陷,而是設計取捨。掌握擴容轉換、掩碼修正、無符號右移、標準庫工具方法,配合 BigInteger 兜底,即可優雅處理所有無符號場景,代碼既安全又高效。