核心概念
byte的范围: Java 中的byte是一个 8 位有符号整数,其范围是-128到127。- 十六进制表示: 一个
byte(8位) 可以用两个十六进制字符(4位一组)来表示。0x0F(15) 和0xF0(240)。 - 负数的表示: Java 中负数的二进制是使用“补码”表示的。
-1的byte值是0b11111111,其二进制补码就是它自己,这个byte转换为十六进制就是0xFF。
使用 Java 8+ 内置的 Hex 类 (推荐)
这是最现代、最简洁、最安全的方法,无需引入第三方库,Java 8 引入了 java.util.Base64,但没有直接提供 Hex 类,在 java.util 包下有一个隐藏的 Hex 类,我们可以通过反射来使用它,或者,更常见的做法是使用 Apache Commons Codec 或 Guava 库。

方案 1.1: 使用反射调用 java.util.Hex (无依赖)
这是一个巧妙的方法,可以避免引入外部库。
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
public class HexUtilWithReflection {
private static final Method ENCODE_METHOD;
private static final Method DECODE_METHOD;
static {
try {
// 获取 java.util.Hex 类
Class<?> hexClass = Class.forName("java.util.Hex");
// 获取 encodeHex 方法
ENCODE_METHOD = hexClass.getMethod("encodeHex", byte[].class);
// 获取 decodeHex 方法
DECODE_METHOD = hexClass.getMethod("decodeHex", char[].class);
} catch (Exception e) {
throw new RuntimeException("Failed to initialize HexUtil", e);
}
}
/**
* 将 byte[] 转换为十六进制字符串
* @param bytes 字节数组
* @return 十六进制字符串 ( "48656C6C6F")
*/
public static String bytesToHex(byte[] bytes) {
if (bytes == null) {
return null;
}
try {
char[] chars = (char[]) ENCODE_METHOD.invoke(null, bytes);
return new String(chars);
} catch (Exception e) {
throw new RuntimeException("Failed to encode bytes to hex", e);
}
}
/**
* 将十六进制字符串转换为 byte[]
* @param hexString 十六进制字符串
* @return 字节数组
*/
public static byte[] hexToBytes(String hexString) {
if (hexString == null || hexString.length() % 2 != 0) {
throw new IllegalArgumentException("Hex string must be non-null and have an even length");
}
try {
char[] chars = hexString.toCharArray();
return (byte[]) DECODE_METHOD.invoke(null, chars);
} catch (Exception e) {
throw new RuntimeException("Failed to decode hex to bytes", e);
}
}
public static void main(String[] args) {
String originalString = "Hello, World! 你好";
byte[] bytes = originalString.getBytes(StandardCharsets.UTF_8);
// byte[] -> Hex String
String hexString = bytesToHex(bytes);
System.out.println("Hex String: " + hexString);
// Hex String -> byte[]
byte[] decodedBytes = hexToBytes(hexString);
String decodedString = new String(decodedBytes, StandardCharsets.UTF_8);
System.out.println("Decoded String: " + decodedString);
}
}
方案 1.2: 使用 Apache Commons Codec (业界标准)
这是最流行、最可靠的方法,几乎所有项目都会用到这个库。
添加 Maven 依赖:
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.15</version> <!-- 使用最新版本 -->
</dependency>
使用 Hex 类:
import org.apache.commons.codec.binary.Hex;
public class HexUtilWithCommonsCodec {
public static void main(String[] args) {
String originalString = "Hello, World!";
byte[] bytes = originalString.getBytes(StandardCharsets.UTF_8);
// byte[] -> Hex String
// Hex.encodeHexString() 方法直接返回 String
String hexString = Hex.encodeHexString(bytes);
System.out.println("Hex String: " + hexString); // 输出: 48656c6c6f2c20576f726c6421
// Hex String -> byte[]
// Hex.decodeHex() 方法接收一个 char[] 或 String
try {
byte[] decodedBytes = Hex.decodeHex(hexString.toCharArray());
String decodedString = new String(decodedBytes, StandardCharsets.UTF_8);
System.out.println("Decoded String: " + decodedString); // 输出: Hello, World!
} catch (Exception e) {
e.printStackTrace();
}
}
}
手动实现 (面试和底层理解)
手动实现有助于理解底层原理,但在生产环境中不推荐,因为它容易出错且性能可能不如优化过的库。
方案 2.1: byte[] 转 String
public class ManualByteToHex {
private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();
public static String bytesToHex(byte[] bytes) {
if (bytes == null) {
return null;
}
// 创建一个 char 数组,每个 byte 对应两个字符
char[] hexChars = new char[bytes.length * 2];
for (int i = 0; i < bytes.length; i++) {
// 将 byte 转换为无符号整数 (0-255)
int v = bytes[i] & 0xFF;
// 取高4位和低4位,分别转换为字符
hexChars[i * 2] = HEX_ARRAY[v >>> 4]; // >>> 是无符号右移
hexChars[i * 2 + 1] = HEX_ARRAY[v & 0x0F];
}
return new String(hexChars);
}
public static void main(String[] args) {
byte[] bytes = {0x48, 0x65, 0x6C, 0x6C, 0x6F, (byte) 0xFF}; // (byte) 0xFF 是 -1
String hexString = bytesToHex(bytes);
System.out.println("Hex String: " + hexString); // 输出: 48656C6C6C6FFF
}
}
方案 2.2: String 转 byte[]
public class ManualHexToByte {
public static byte[] hexToBytes(String hexString) {
if (hexString == null || hexString.length() % 2 != 0) {
throw new IllegalArgumentException("Invalid hex string.");
}
byte[] bytes = new byte[hexString.length() / 2];
for (int i = 0; i < bytes.length; i++) {
// 每两个字符解析为一个 byte
int highNibble = Character.digit(hexString.charAt(i * 2), 16); // 取高4位
int lowNibble = Character.digit(hexString.charAt(i * 2 + 1), 16); // 取低4位
bytes[i] = (byte) ((highNibble << 4) | lowNibble);
}
return bytes;
}
public static void main(String[] args) {
String hexString = "48656C6C6C6FFF";
byte[] bytes = hexToBytes(hexString);
// 打印 byte 数组内容
for (byte b : bytes) {
System.out.printf("%02X ", b); // %02X 保证输出两位大写十六进制
}
System.out.println();
}
}
使用 BigInteger (简单但性能略低)
这个方法非常简洁,适合一次性转换,但循环性能不如手动方法。
方案 3.1: byte[] 转 String
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
public class BigIntegerByteToHex {
public static String bytesToHex(byte[] bytes) {
if (bytes == null) {
return null;
}
// 将 byte[] 视为一个正数,然后转换为十六进制字符串
BigInteger bigInt = new BigInteger(1, bytes); // 参数 1 表示正数
return bigInt.toString(16);
}
public static void main(String[] args) {
byte[] bytes = {0x00, 0x01, 0x02, (byte) 0xFF};
String hexString = bytesToHex(bytes);
System.out.println("Hex String: " + hexString); // 输出: 102ff
// 注意:前导零会被省略
}
}
方案 3.2: String 转 byte[]
import java.math.BigInteger;
public class BigIntegerHexToByte {
public static byte[] hexToBytes(String hexString) {
if (hexString == null) {
return null;
}
BigInteger bigInt = new BigInteger(hexString, 16);
// toByteArray() 会返回一个 byte[],但可能包含一个前导的 0x00,因为 BigInteger 是任意精度的
byte[] bytes = bigInt.toByteArray();
// 处理前导零的问题
// "FF" -> byte[] { -1 },但 "00FF" -> byte[] { 0, -1 }
// 如果原始字符串长度是奇数,说明丢失了一个前导零
if (hexString.length() % 2 != 0) {
byte[] temp = new byte[bytes.length + 1];
System.arraycopy(bytes, 0, temp, 1, bytes.length);
temp[0] = 0;
return temp;
}
return bytes;
}
public static void main(String[] args) {
String hexString = "102ff";
byte[] bytes = hexToBytes(hexString);
for (byte b : bytes) {
System.out.printf("%02X ", b);
}
System.out.println(); // 输出: 01 02 FF
}
}
总结与推荐
| 方法 | 优点 | 缺点 | 推荐场景 |
|---|---|---|---|
| Apache Commons Codec | 代码简洁、性能高、稳定可靠、业界标准 | 需要引入第三方依赖 | 生产环境首选 |
| 手动实现 | 无依赖、有助于理解底层 | 代码繁琐、易出错、性能需自己优化 | 学习、面试、无法引入依赖的极端环境 |
| BigInteger | 代码非常简洁 | 性能较低、有前导零等边界问题处理麻烦 | 一次性转换、代码行数要求极低的场景 |
反射调用 java.util.Hex |
无依赖、是 Java 官方实现 | 使用反射,不够优雅,未来可能失效 | 不想引入第三方库,且愿意使用“黑科技”的场景 |
最终建议:
- 如果你正在构建一个新的项目或可以使用 Maven/Gradle,强烈推荐使用 Apache Commons Codec,这是最专业、最不容易出错的选择。
- 如果你在面试中被问到这个问题,展示手动实现的能力非常重要,这能体现你对数据类型的深刻理解。
- 如果你在一个无法添加依赖的旧项目中,手动实现是唯一的选择。
