java字符编码需全程统一charset,内存用utf-16,io须显式指定编码;string在jdk9+采用紧凑字符串优化,length()返回char数非码点数;getbytes()与new string()必须配对使用相同charset,推荐standardcharsets常量。

Java 字符编码不是“选个 charset 就完事”的简单操作,它贯穿字符串创建、内存存储、IO 传输和跨系统交互全过程。核心在于理解:Java 内存中统一用 Unicode(UTF-16 表示),而字节序列(如文件、网络、数据库)必须显式指定编码规则;二者不匹配,乱码必然发生。
Java 中 char 和 String 的真实内存占用
Java 的 char 类型固定占 2 字节,这是 UTF-16 编码的基本单位。但 String 的实际内存开销并不等于 length() × 2:
- JDK 9+ 启用“紧凑字符串”优化:若字符串只含 Latin-1 字符(U+0000–U+00FF),内部用
byte[]存储,每个字符仅占 1 字节;含中文等字符时,才回退到 UTF-16 编码的byte[]或兼容结构。 -
String.length()返回的是 char 数量,不是 Unicode 码点数量。遇到代理对(如某些 emoji),一个字符需两个 char 表示,length()会返回 2,但实际只对应 1 个码点。 - 不要用
str.getBytes().length反推字符数——它取决于编码方式(UTF-8 下“你”是 3 字节,GBK 下是 2 字节,ISO-8859-1 下会丢数据)。
getBytes() 与 new String() 的配对逻辑
这两个方法是编码转换的“一对反操作”,必须严格保持 charset 一致,否则解码失败或乱码:
-
str.getBytes("GBK"):把内存中的 Unicode 字符串,按 GBK 规则转成字节流(如“中”→0xD6 0xD0)。 -
new String(bytes, "GBK"):把字节流按 GBK 规则解释为 Unicode 字符,重建 String 对象(0xD6 0xD0→ “中”)。 - 常见错误:
str.getBytes("UTF-8")得到的字节数组,用new String(bytes, "GBK")解码——结果是乱码,因为字节序列本不是 GBK 编码生成的。 - 推荐写法:始终使用
StandardCharsets常量(如StandardCharsets.UTF_8),避免传字符串名引发UnsupportedEncodingException。
跨编码场景下的安全转换实践
从 UTF-8 文件读取中文内容再保存为 GBK,不能靠“猜”或平台默认值,需明确每一步的编解码意图:
- 读文件时:用
InputStreamReader(new FileInputStream(f), StandardCharsets.UTF_8),确保字节正确解码为内存中的 Unicode 字符串。 - 处理过程中:所有操作都在 String 层进行,无需关心底层编码,Java 自动按 UTF-16 处理。
- 写文件时:用
OutputStreamWriter(new FileOutputStream(f), "GBK"),将 Unicode 字符串编码为 GBK 字节流写入磁盘。 - 避免陷阱:不用
String.getBytes()(无参)或new String(byte[])(无参),它们依赖file.encoding系统属性,极易在不同环境行为不一致。
排查乱码问题的关键检查点
出现问号、方块、异常符号时,按顺序确认以下四层是否一致:
- 源数据原始编码(如文件保存时用的是 UTF-8 还是 GBK?HTTP 响应头声明了什么 charset?)
- Java 读取时使用的解码 Charset(
InputStreamReader或new String(bytes, cs)是否匹配?) - 内存中 String 内容是否正确(可通过日志打印
str.codePoints().forEach(System.out::println)查看码点) - 输出时使用的编码 Charset(写文件、发 HTTP 响应、打印控制台是否用了正确 charset?终端本身是否支持该编码?)
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











