Java中String内存始终用UTF-16存储:BMP字符占2字节、1个char,代理对字符占4字节、2个char;s.length()返回char数而非Unicode字符数,需用codePointCount获取真实字符数;getBytes()是编码转换而非直接取内存字节;文件与网络I/O必须显式指定Charset,全流程统一UTF-8并显式声明编码可避免乱码。

Java中字符编码和内存占用不是简单的一一对应关系,关键在于区分“内存表示”和“序列化格式”两个层面。
String在内存中始终是Unicode(UTF-16)
Java的String对象在JVM堆内存中以UTF-16编码存储:每个基本平面字符(如英文字母、常用汉字)占2字节,一个char;超出BMP的字符(如部分emoji、古汉字)用代理对表示,占4字节、对应两个char。
- 调用
s.length()返回的是char数量,不是真实字符数;需用s.codePointCount(0, s.length())获取Unicode字符总数 -
s.charAt(i)只适合访问BMP字符;安全遍历应使用codePoints().forEach(...)或手动处理代理对 - 字符串常量池中的字符串、new出来的字符串,底层都按UTF-16布局,与源文件编码无关
getBytes()转换的是编码格式,不是“提取内存原始字节”
调用string.getBytes(Charset)本质是:把内存里的UTF-16字符串,先解码成Unicode码点,再按指定编码规则(如UTF-8、GBK)重新编码为字节数组。结果长度完全由目标编码决定。
- 英文字符:UTF-8 → 1字节,GBK → 1字节,UTF-16BE → 2字节
- 中文字符“中”(U+4E2D):UTF-8 → 3字节(0xE4 0xB8 0xAD),GBK → 2字节(0xD6 0xD0),UTF-16BE → 2字节(0x4E 0x2D)
- emoji“?”(U+1F60A):UTF-8 → 4字节,GBK → 不支持(抛
UnsupportedEncodingException),UTF-16BE → 4字节(代理对:0xD83D 0xDE0A)
文件读写必须显式指定Charset
Java I/O默认使用JVM启动时确定的Charset.defaultCharset()(Windows常为GBK,Linux/macOS常为UTF-8),但该值不可靠且易被环境覆盖。硬编码或依赖默认值极易引发乱码。
立即学习“Java免费学习笔记(深入)”;
- 读文件:
Files.readString(path, StandardCharsets.UTF_8)或new InputStreamReader(Files.newInputStream(path), StandardCharsets.UTF_8) - 写文件:
Files.writeString(path, content, StandardCharsets.UTF_8)或new OutputStreamWriter(Files.newOutputStream(path), StandardCharsets.UTF_8) - 网络传输(如HTTP响应):务必设置
Content-Type: text/plain; charset=utf-8头部
常见乱码根源与修复路径
乱码本质是“编码写入”和“解码读取”用的不是同一套规则。典型场景有三类:
- 源码文件保存为UTF-8,但编译时未指定
-encoding UTF-8→ 中文字符串字面量被错误解析 → 编译后class文件含错码 → 运行时报错或显示方块 - 数据库字段用UTF-8存,JDBC连接URL没加
?useUnicode=true&characterEncoding=UTF-8→ Java读出乱码 - 前端表单提交未设
accept-charset="UTF-8",或后端Servlet未调用request.setCharacterEncoding("UTF-8")→ POST参数乱码
修复核心原则:全流程统一使用UTF-8,并在每处I/O边界显式声明编码。不复杂但容易忽略。


















