Java内存中String默认使用UTF-16编码,磁盘IO则依赖系统默认编码(如UTF-8或GBK),二者用途不同、不可混用;显式指定Charset(如StandardCharsets.UTF_8)可避免乱码。

Java内存中字符串默认使用UTF-16编码,而磁盘IO(如文件读写、网络传输)通常依赖系统默认编码或显式指定的字符集(如UTF-8),二者本质不同、用途不同、不可混为一谈。
内存中String为何是UTF-16
Java语言规范规定:String内部以char数组(Java 8及以前)或byte数组+编码标识(Java 9+紧凑字符串优化后)存储,逻辑上基于UTF-16编码。每个char占2字节,可表示BMP(基本多文种平面)内的字符;超出BMP的字符(如某些emoji、古汉字)用一对char(即代理对,surrogate pair)表示,共4字节。
- 这是JVM内部实现细节,对开发者透明——你调用
str.length()得到的是char个数,不是Unicode码点数;要获取真实字符数,需用str.codePointCount(0, str.length()) - String不可变,所有字符串操作(拼接、截取等)都基于这个UTF-16视图进行,不涉及编码转换
- 注意:
"?".length()返回2,因为该emoji是U+1F60A,属于增补字符,需用两个char表示
磁盘IO默认用什么编码?为什么容易出乱码
Java的IO类(如FileReader、PrintWriter)若未显式指定charset,会使用Charset.defaultCharset()——它取决于操作系统和JVM启动参数,常见为UTF-8(Linux/macOS)、GBK(Windows中文版),但**不是UTF-16**。
-
new FileWriter("a.txt")→ 使用默认编码写入,内容按该编码转为字节存入磁盘 -
new FileReader("a.txt")→ 按同一默认编码将字节解码为char,重建String - 若文件实际是UTF-8保存,但在GBK环境下用
FileReader读,就会出现乱码——这不是内存编码错了,而是读写时编解码不匹配
如何安全地做磁盘IO(推荐做法)
绕过默认编码陷阱,始终显式指定字符集。优先使用NIO.2的Files工具类,它强制要求传入Charset:
立即学习“Java免费学习笔记(深入)”;
- 写文件:
Files.write(Paths.get("a.txt"), "你好".getBytes(StandardCharsets.UTF_8), StandardCharsets.UTF_8)(更推荐下面方式) - 正确写:
Files.write(Paths.get("a.txt"), "你好".getBytes(StandardCharsets.UTF_8), StandardCharsets.UTF_8)→ 实际应使用Files.write(path, lines, charset)或Files.writeString()(Java 11+) - Java 11+简洁写法:
Files.writeString(Paths.get("a.txt"), "你好", StandardCharsets.UTF_8) - 读文件:
String s = Files.readString(Paths.get("a.txt"), StandardCharsets.UTF_8) - 若必须用传统IO,请用
InputStreamReader(new FileInputStream(f), StandardCharsets.UTF_8)替代FileReader
UTF-16在磁盘上真的不能用吗?
可以,但极少用。UTF-16是面向内存和API交互设计的,磁盘存储更倾向UTF-8:
- UTF-8兼容ASCII,英文文本体积小;无字节序问题(UTF-16有BOM和BE/LE之分)
- Java支持UTF-16文件读写,例如:
Files.write(p, bytes, StandardCharsets.UTF_16),但需注意:写入时默认带BOM(\uFEFF),读取时需能识别BOM才能正确判断字节序 - 除非对接特定协议(如Windows注册表、部分旧Windows API),否则不建议主动用UTF-16存文件


















