核心概念:字符编码
在开始转换之前,必须理解字符编码。

String: 在 Java 中,String内部使用 UTF-16 编码来表示字符,这意味着一个char类型通常占用 2 个字节。byte[]: 这是一个原始字节的数组,它本身不包含任何关于字符或编码的信息,它只是数据的二进制表示。
当你在 String 和 byte[] 之间转换时,你必须明确指定使用哪种编码规则(如 UTF-8, ISO-8859-1, GBK 等)。如果编码不一致,就会出现乱码(mojibake)。
为什么 UTF-8 是最佳选择?
UTF-8 是目前互联网上最广泛使用的编码,它具有以下优点:
- 兼容 ASCII:对于英文字符,
UTF-8的编码与ASCII完全相同。 - 变长编码:它可以使用 1 到 4 个字节来表示一个字符,非常节省空间。
- 无 BOM:通常不需要字节顺序标记。
- 通用性强:可以表示世界上几乎所有的字符。
String 转 byte[]
使用 String 类的 getBytes() 方法。
显式指定编码(推荐)
这是最安全、最推荐的方式,你明确告诉 JVM 使用哪种编码来将字符串转换为字节数组。

import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
public class StringToByteArray {
public static void main(String[] args) {
String str = "Hello, 世界!";
// --- 推荐方式:使用 StandardCharsets (Java 7+) ---
// StandardCharsets 是一个枚举类,提供了几种常用编码的常量,避免了拼写错误。
try {
byte[] utf8Bytes = str.getBytes(StandardCharsets.UTF_8);
System.out.println("使用 UTF-8 编码的字节数组: " + java.util.Arrays.toString(utf8Bytes));
// 也可以使用字符串字面量
byte[] gbkBytes = str.getBytes("GBK");
System.out.println("使用 GBK 编码的字节数组: " + java.util.Arrays.toString(gbkBytes));
} catch (UnsupportedEncodingException e) {
// 在使用 StandardCharsets 时,这个异常通常不会发生
e.printStackTrace();
}
}
}
输出分析:
Hello, 世界!的UTF-8编码字节数组:H-> 72e-> 101l-> 108l-> 108o-> 111- -> 44
- ` ` -> 32
世-> -28, -72, -83 (UTF-8 中,中文字符通常占3个字节)界-> -27, -101, -67- -> 33
GBK编码中,中文字符通常占2个字节,世和界的字节表示会与UTF-8不同。
使用平台默认编码(不推荐)
如果你不指定编码,getBytes() 会使用 JVM 运行所在平台的默认字符编码。
String str = "Hello, 世界!";
// --- 不推荐方式:使用平台默认编码 ---
// 这种代码在不同操作系统(如 Windows vs. Linux)或不同配置的机器上,
// 可能会产生不同的结果,导致乱码问题。
byte[] defaultBytes = str.getBytes();
System.out.println("使用默认编码的字节数组: " + java.util.Arrays.toString(defaultBytes));
为什么不推荐?
因为代码的可预测性差,你的开发环境可能是 UTF-8,但生产服务器可能是 GBK,这会导致难以排查的乱码问题。
byte[] 转 String
使用 String 的构造函数 String(byte[] bytes, String charsetName)。

显式指定编码(推荐)
这是唯一能正确还原出原始字符串的方式。
import java.nio.charset.StandardCharsets;
public class ByteArrayToString {
public static void main(String[] args) {
byte[] utf8Bytes = {(byte) 72, (byte) 101, (byte) 108, (byte) 108, (byte) 111, (byte) 44, (byte) 32, (byte) -28, (byte) -72, (byte) -83, (byte) -27, (byte) -101, (byte) -67, (byte) 33};
// --- 推荐方式:使用 StandardCharsets ---
// 必须使用与当初编码时完全相同的编码,才能正确还原字符串。
String strFromUtf8 = new String(utf8Bytes, StandardCharsets.UTF_8);
System.out.println("从 UTF-8 字节数组还原的字符串: " + strFromUtf8);
// --- 错误示例:编码不一致 ---
// 如果用错误的编码来解码,就会出现乱码
String wrongStr = new String(utf8Bytes, StandardCharsets.ISO_8859_1);
System.out.println("用 ISO-8859-1 错误解码后的字符串: " + wrongStr); // 输出类似 "Hello, 世畎!"
}
}
使用平台默认编码(不推荐)
与 getBytes() 类似,如果不指定编码,构造函数会使用平台默认编码。
byte[] someBytes = {(byte) 72, (byte) 101, (byte) 108, (byte) 108, (byte) 111};
// --- 不推荐方式:使用平台默认编码 ---
String strFromDefault = new String(someBytes);
System.out.println("使用默认编码还原的字符串: " + strFromDefault);
同样,这种方式依赖于环境,是不可靠的。
特殊情况:ISO-8859-1 编码
ISO-8859-1(也称为 Latin-1)是一种非常特殊的编码,它在 String 和 byte[] 转换中有独特的用途。
- 特点:
ISO-8859-1的编码范围是 0-255,它对字节的值不做任何修改,也就是说,一个字节0x41在ISO-8859-1中就是字符'A',反过来,字符'A'转换成ISO-8859-1的字节就是0x41。 - 用途:这种“不修改”的特性使得
ISO-8859-1成为一个完美的“中间媒介”或“无损通道”。
场景: 当你不确定一个 byte[] 的原始编码,但又想安全地将其转换成 String(之后再转换回 byte[])而不丢失任何字节时,可以使用 ISO-8859-1。
public class Iso8859_1Example {
public static void main(String[] args) {
// 假设我们有一个来自未知来源的 byte[]
byte[] unknownSourceBytes = {(byte) 0xE4, (byte) 0xBD, (byte) 0xA0, (byte) 0xE5, (byte) 0xA5, (byte) 0xBD}; // "你好" 的 GBK 编码
// 1. 使用 ISO-8859-1 将 byte[] 转为 String
// 这个 String 里面的字符对应着原始的字节,但内容是乱码的。
String intermediateStr = new String(unknownSourceBytes, StandardCharsets.ISO_8859_1);
System.out.println("中间 String (内容乱码): " + intermediateStr); // 输出类似 "ä½ å¥½"
// 2. 将这个 String 再用 ISO-8859-1 转回 byte[]
// 因为 ISO-8859-1 是无损的,所以得到的 byte[] 和原始的完全一样!
byte[] recoveredBytes = intermediateStr.getBytes(StandardCharsets.ISO_8859_1);
System.out.println("原始字节数组: " + java.util.Arrays.toString(unknownSourceBytes));
System.out.println("恢复后的字节数组: " + java.util.Arrays.toString(recoveredBytes));
System.out.println("是否相等: " + java.util.Arrays.equals(unknownSourceBytes, recoveredBytes)); // 输出 true
}
}
注意:这种技术只保证了字节数据的完整性,不保证字符的可读性,当你需要处理一个 byte[],但又不想因为编码问题丢失数据时,这是一个非常实用的技巧。
总结与最佳实践
| 转换方向 | 推荐方法 | 代码示例 | 关键点 |
|---|---|---|---|
String -> byte[] |
显式指定编码 | byte[] bytes = myString.getBytes(StandardCharsets.UTF_8); |
必须指定编码,推荐 UTF-8。 |
byte[] -> String |
显式指定编码 | String str = new String(myBytes, StandardCharsets.UTF_8); |
必须使用与编码时相同的编码,否则乱码。 |
| 特殊情况 | 使用 ISO-8859-1 作为无损通道 |
new String(bytes, "ISO-8859-1") 和 str.getBytes("ISO-8859-1") |
用于在不丢失字节的情况下安全地“包装”byte[]。 |
核心原则:
- 永远不要依赖平台默认编码。
- 在
String和byte[]之间转换时,始终显式地、明确地指定字符编码。 - 优先使用
StandardCharsets.UTF_8,除非你有特殊需求(如处理旧系统或特定地区的文本)。 - 当需要无损地保存和恢复字节数组时,
ISO-8859-1是一个可靠的工具。
