Java字符处理分内存内(统一UTF-16)和进出时(须显式指定Charset)两层,乱码几乎全因进出环节编码解码不匹配;String在JVM中恒为UTF-16,与外部交互必须用StandardCharsets明确编解码,否则依赖系统默认值易致乱码。

直接说核心:Java 里字符处理分“内存中”和“进出时”两层,乱码几乎都出在进出环节没对齐。
内存里统一用 UTF-16
Java 所有 String 和 char 在 JVM 内部永远是 UTF-16 编码。比如 '中' 存的是 Unicode 码点 U+4E2D,占 2 个字节;emoji ? 这类超出基本平面的字符,会用两个 char(代理对)表示。你调用 s.length() 得到的是 char 数量,不是真实字符数——想准确计数得用 codePointCount()。
进出 JVM 必须显式指定编码
文件读写、网络传输、控制台打印……这些操作都涉及字节(byte[]),而字节和字符串之间没有自动魔法。必须明确告诉 JVM:“这个字节数组是按什么规则编出来的?我要按什么规则把它变回去?”
- 编码(String → byte[]):用
str.getBytes(StandardCharsets.UTF_8) - 解码(byte[] → String):用
new String(bytes, StandardCharsets.UTF_8) - 漏掉 Charset 参数?那就用系统默认值(Windows 中文版常是 GBK,Linux/macOS 常是 UTF-8),一换环境就崩
源文件编码和编译器要一致
你的 .java 文件本身是文本文件,它也有编码。如果文件存为 UTF-8(现代 IDE 默认),但 javac 没加 -encoding UTF-8,它就会用系统默认编码(比如 windows-1251)去读 UTF-8 字节流——一个中文被拆成两个乱码字符,后续 getBytes() 就彻底失真。所以编译命令里写死 javac -encoding UTF-8 是基本操作。
立即学习“Java免费学习笔记(深入)”;
转换本质是“先解再编”
UTF-8 转 GBK 不是直接改字节,而是三步走:
- 把 UTF-8 字节流按 UTF-8 规则解码成 Unicode 字符串(进内存)
- 这个字符串在内存里已经是标准 Unicode
- 再按 GBK 规则编码成新字节流(出内存)
中间这一步 Unicode 是桥梁,也是 Java “一次编写,到处运行”的关键支撑。


















