java中char是utf-16代码单元,固定占2字节,可直接表示bmp字符(如'中'),超出bmp的字符(如?)需用两个char组成的代理对表示;string.length()返回代码单元数而非真实字符数,应使用codepoint相关api处理。

Java 字符型的编码处理和内存存储不是两个孤立概念,而是同一过程的内外两面:外部是字节流如何被解释成字符(编码/解码),内部是字符在 JVM 中如何被表示和运算(Unicode 存储与代理对)。搞不清这点,就容易在文件读写、Emoji 处理、字符串长度计算等场景踩坑。
char 类型本质:2 字节代码单元,不是“一个字符”
Java 的 char 是 UTF-16 编码的代码单元(Code Unit),固定占 2 字节,取值范围 0x0000–0xFFFF。它能直接表示基本多语言平面(BMP)里的字符,比如 'A'、'中'、'€';但对超出 BMP 的字符(如 ? U+1F680、? U+20BB7),必须用两个 char 组成代理对(Surrogate Pair)才能完整表达。
- 单个 char 变量只能存一个代码单元,不能保证对应人类认知中的“一个字符”
- String.length() 返回的是代码单元数量,不是真实字符数。含 Emoji 的字符串 length 可能比肉眼看到的字符数多 1
- 遍历 String 时用
String.codePoints().forEach(...)才能正确按码点(Code Point)处理每个逻辑字符
内存中字符永远以 Unicode 形式存在
无论源文件用什么编码(UTF-8、GBK、ISO-8859-1),只要成功编译进 class 文件,或通过正确解码读入 JVM,所有 char 和 String 在内存中都统一为 Unicode 表示——即 UTF-16 编码的代码单元序列。
- 声明
char c = '中';时,JVM 直接把 U+4E2D 存为 0x4E2D(2 字节) - 用
new String(bytes, StandardCharsets.UTF_8)解码字节数组,就是把 UTF-8 字节流还原为内存中的 UTF-16 序列 - 反向操作
str.getBytes(StandardCharsets.UTF_8),则是把内存里的 UTF-16 转成指定编码的字节流
文件读写乱码的根源与解法
乱码几乎总是因为编码声明与实际字节流不匹配。FileReader/FileWriter 默认用系统编码(中文 Windows 常为 GBK),若文件是 UTF-8 却没指定,就会错解。
- 避免用 FileReader/FileWriter 处理非系统默认编码的文本;改用 InputStreamReader + FileInputStream,显式传入 Charset
- 写文件时优先用 OutputStreamWriter + FileOutputStream,并指定 UTF-8
- Maven/Gradle 项目务必设置源码编码为 UTF-8(
file.encoding=UTF-8),防止 .java 文件本身编译出错
实战建议:从声明到传输的编码闭环
一个健壮的文本处理流程应明确每一步的编码意图:
- 源码文件保存为 UTF-8,并在 IDE 和构建工具中统一配置
- 读文件:用
Files.readString(path, UTF_8)或new InputStreamReader(new FileInputStream(), UTF_8) - 字符串处理:基于 Unicode 码点操作(
String.codePointAt()、Character.isLetter()等) - 写文件或发 HTTP 请求:显式调用
getBytes(UTF_8)或设置 Content-Type header 为text/plain; charset=utf-8
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











