计算机世界里最基础的矛盾之一,就是如何用二进制数字表示人类文字。上世纪60年代诞生的ASCII编码(American Standard Code for Information Interchange)用7位二进制数(共128个码位)定义了英文字母、数字和常用符号。这种设计在英语世界运行良好,但遇到中文、日文等字符体系时就捉襟见肘。
我刚开始学编程时,经常遇到控制台输出乱码的情况。后来才明白,这背后是字符编码的"巴别塔困境"——不同地区各自为政开发了GB2312(中国)、Shift_JIS(日本)等编码方案,导致同一份文档在不同系统打开可能显示为天书。直到Unicode的出现,才真正实现了"书同文"的数字版。
ASCII编码的巧妙之处在于:
注意:Java中的char类型采用UTF-16编码,但ASCII字符在UTF-16中与原始ASCII值一致。例如在Java中执行
(int)'A'仍会得到65。
Unicode的核心突破在于:
Java语言从诞生就采用Unicode作为基础字符集,这也是为什么Java能原生支持多语言变量名:
java复制int 计数器 = 10; // 合法的Java代码
UTF-8的聪明之处在于:
以汉字"汉"(U+6C49)为例:
Java中最容易踩坑的场景就是字符串与字节数组的相互转换:
java复制String text = "你好世界";
byte[] utf8Bytes = text.getBytes(StandardCharsets.UTF_8); // 显式指定编码
String recovered = new String(utf8Bytes, StandardCharsets.UTF_8);
血泪教训:永远不要使用无参数的getBytes()方法,其行为取决于默认字符集,可能在不同环境产生不同结果。
处理文本文件时经常需要处理BOM(Byte Order Mark)头。Windows生成的UTF-8文件常带有EF BB BF前缀:
java复制InputStream input = new FileInputStream("data.txt");
BOMInputStream bomIn = new BOMInputStream(input); // Apache Commons IO
String charsetName = bomIn.hasBOM() ? "UTF-8" : detectCharset(bomIn);
Java内部采用UTF-16编码存储字符串,但存在优化机制:
验证字符串实际占用空间的方法:
java复制long size = java.lang.instrument.Instrumentation.getObjectSize(myString);
file -i命令检测编码案例一:数据库乱码
?useUnicode=true&characterEncoding=UTF-8案例二:CSV文件Excel打开乱码
CharsetDecoder/CharsetEncoder替代String转换\uXXXX形式更可靠ISO-8859-1编码避免UTF-8解码开销经过多年项目历练,我的编码选择策略是:
最后分享一个实用技巧:在IDE设置中将所有文件编码设为UTF-8,同时在构建脚本中加入编码参数:
gradle复制tasks.withType(JavaCompile) {
options.encoding = "UTF-8"
}