一个「字」在 Java 里有 char、码点、字素、字节四种数法;用错字符集又会出现各种乱码和问号。这篇逐项真实运行,并在 JDK 17、21、27 下对比默认字符集在不同 LC_ALL 下的结果。
文本 length 码点数 字素数 UTF-8字节 UTF-16字节
字母 a 1 1 1/1 1 2
汉字 中 1 1 1/1 3 2
表情 😀 U+1F600 2 1 1/1 4 4
é 预组合 U+00E9 1 1 1/1 2 2
é 组合 e+U+0301 2 2 1/1 3 4
家庭 👨👩👧(ZWJ 序列) 8 5 1/1 18 16
(字素数一列:BreakIterator / 正则 \X 两种方法的结果)
"a😀b".charAt(1) = U+D83D(高代理),charAt(2) = U+DE00(低代理);Character.isSurrogate(charAt(1)) = true
new StringBuilder("a😀b").reverse() = 代码点正确反转:[62, 1f600, 61](StringBuilder.reverse 会处理代理对)
按 char 倒序拼接得到 [62, de00, d83d, 61](代理对被拆开且顺序反了)
"a😀b".substring(0, 2) = 61 3f(UTF-8 字节;截在代理对中间,只剩一个孤立的高代理,编码成 ? 即 3f)原文 "中文é😀" 的 UTF-8 字节:e4 b8 ad e6 96 87 c3 a9 f0 9f 98 80(12 字节) 按 UTF-8 解码 → "中文é😀" 长度 5,是否等于原文 true 按 ISO-8859-1 解码 → "ä¸æÃ©ð" 长度 12,是否等于原文 false 按 GBK 解码 → "涓枃茅馃榾" 长度 6,是否等于原文 false 按 US-ASCII 解码 → "������������" 长度 12,是否等于原文 false 按 UTF-16 解码 → "귦隇쎩颀" 长度 6,是否等于原文 false 用 ISO-8859-1 编码原文 → 3f 3f e9 3f(4 字节) 用 US-ASCII 编码原文 → 3f 3f 3f 3f(4 字节) 用 GBK 编码原文 → d6 d0 ce c4 a8 a6 3f(7 字节) 损坏的 UTF-8 字节 e4 b8 78 ff → new String(...) 得到码点 [U+FFFD, U+0078, U+FFFD](默认替换成 U+FFFD) CharsetDecoder + REPORT → MalformedInputException: Input length = 2 US_ASCII 编码器编码 é + 默认 REPORT → UnmappableCharacterException: Input length = 1 在 UTF-8 的 "中文" 第 4 字节处截断(字节 e4 b8 ad e6)→ "中�"(长度 2)
[JDK 17,LC_ALL=C] JDK 17.0.20.1, defaultCharset = US-ASCII, file.encoding = US-ASCII, native.encoding = US-ASCII, stdout.encoding = null FileWriter wrote U+4E2D as bytes: 3f; getBytes() = 3f; stdout bytes of println: see below ? [JDK 17,LC_ALL=en_US.UTF-8] JDK 17.0.20.1, defaultCharset = UTF-8, file.encoding = UTF-8, native.encoding = UTF-8, stdout.encoding = null FileWriter wrote U+4E2D as bytes: e4 b8 ad; getBytes() = e4 b8 ad; stdout bytes of println: see below 中 [JDK 21,LC_ALL=C] JDK 21.0.12.1, defaultCharset = UTF-8, file.encoding = UTF-8, native.encoding = US-ASCII, stdout.encoding = US-ASCII FileWriter wrote U+4E2D as bytes: e4 b8 ad; getBytes() = e4 b8 ad; stdout bytes of println: see below ? [JDK 21,LC_ALL=zh_CN.GBK] JDK 21.0.12.1, defaultCharset = UTF-8, file.encoding = UTF-8, native.encoding = GBK, stdout.encoding = GBK FileWriter wrote U+4E2D as bytes: e4 b8 ad; getBytes() = e4 b8 ad; stdout bytes of println: see below 中 [JDK 21,LC_ALL=C + -Dfile.encoding=GBK] JDK 21.0.12.1, defaultCharset = UTF-8, file.encoding = GBK, native.encoding = US-ASCII, stdout.encoding = US-ASCII FileWriter wrote U+4E2D as bytes: e4 b8 ad; getBytes() = e4 b8 ad; stdout bytes of println: see below ? [JDK 21,LC_ALL=C + -Dfile.encoding=COMPAT] JDK 21.0.12.1, defaultCharset = US-ASCII, file.encoding = US-ASCII, native.encoding = US-ASCII, stdout.encoding = US-ASCII FileWriter wrote U+4E2D as bytes: 3f; getBytes() = 3f; stdout bytes of println: see below ? [JDK 27,LC_ALL=C] JDK 27, defaultCharset = UTF-8, file.encoding = UTF-8, native.encoding = US-ASCII, stdout.encoding = US-ASCII FileWriter wrote U+4E2D as bytes: e4 b8 ad; getBytes() = e4 b8 ad; stdout bytes of println: see below ?
"\u00e9".equals("e\u0301") = false,两者看起来相同
Normalizer NFC(e+U+0301) 之后 equals 预组合 = true;NFD(é) 之后长度 = 2
Collator 比较两种写法 compare = 0(0 表示相等,Collator 会处理组合序列)
NFKC(fi 连字 U+FB01) = "fi";NFKC(全角 AB) = "AB"
"straße".toUpperCase(Locale.ROOT) = "STRASSE"(长度 6 → 7);toLowerCase(ROOT) = "straße"(长度 6);toLowerCase(tr) = "straße"
"Dž".toUpperCase(Locale.ROOT) = "DŽ"(长度 1 → 1);toLowerCase(ROOT) = "dž"(长度 1);toLowerCase(tr) = "dž"
"I".toUpperCase(Locale.ROOT) = "I"(长度 1 → 1);toLowerCase(ROOT) = "i"(长度 1);toLowerCase(tr) = "ı"
"i".toUpperCase(Locale.ROOT) = "I"(长度 1 → 1);toLowerCase(ROOT) = "i"(长度 1);toLowerCase(tr) = "i"
"İ".toUpperCase(Locale.ROOT) = "İ"(长度 1 → 1);toLowerCase(ROOT) = "i̇"(长度 2);toLowerCase(tr) = "i"
"title".toUpperCase(tr) = "TİTLE",toUpperCase(ROOT) = "TITLE"("i" 在土耳其语里变成带点的 İ)
"ß".equalsIgnoreCase("SS") = false,"ß".equalsIgnoreCase("ẞ") = true