作为一名Java开发者,我经常遇到这样的场景:在Windows开发环境下运行正常的程序,部署到Linux服务器后中文显示就变成了乱码。这种编码问题看似简单,却可能耗费开发者大量时间排查。字符编码问题的本质在于不同系统对字符的二进制表示方式存在差异。
计算机底层存储的都是二进制数据,而字符编码就是字符与二进制之间的映射规则。当编码规则不一致时,就会出现"鸡同鸭讲"的情况。比如:
这种"编码丛林"导致同一个中文字符在不同环境下可能被解释成不同的二进制序列,最终呈现为乱码。
在实际开发中,我遇到过以下几种典型的编码问题:
经过多年实践,我强烈建议所有Java项目统一采用UTF-8编码,原因如下:
注意:MySQL中的"utf8"实际上是阉割版的UTF-8(最多支持3字节),真正的UTF-8应该使用"utf8mb4"字符集。
以IntelliJ IDEA为例:
在pom.xml中添加编码配置:
xml复制<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>
对于.properties文件,建议:
JVM默认会使用操作系统的默认编码,这会导致跨环境问题。解决方案:
bash复制java -Dfile.encoding=UTF-8 -jar app.jar
bash复制JAVA_OPTS="$JAVA_OPTS -Dfile.encoding=UTF-8"
properties复制spring.jvm.arguments=-Dfile.encoding=UTF-8
在代码中添加检查点:
java复制@PostConstruct
public void checkEncoding() {
log.info("Default Charset: {}", Charset.defaultCharset());
log.info("file.encoding: {}", System.getProperty("file.encoding"));
log.info("sun.jnu.encoding: {}", System.getProperty("sun.jnu.encoding"));
}
sql复制CREATE DATABASE mydb
CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
properties复制spring.datasource.url=jdbc:mysql://localhost:3306/mydb?\
useUnicode=true\
&characterEncoding=utf8mb4\
&useSSL=false\
&serverTimezone=Asia/Shanghai\
&allowPublicKeyRetrieval=true
sql复制CREATE DATABASE mydb WITH ENCODING 'UTF8' LC_COLLATE 'en_US.UTF-8' LC_CTYPE 'en_US.UTF-8';
建议在创建数据库时就选择AL32UTF8字符集。
java复制@Bean
public FilterRegistrationBean<CharacterEncodingFilter> filterRegistrationBean() {
FilterRegistrationBean<CharacterEncodingFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new CharacterEncodingFilter());
registrationBean.addInitParameter("encoding", "UTF-8");
registrationBean.addInitParameter("forceEncoding", "true");
registrationBean.addUrlPatterns("/*");
return registrationBean;
}
java复制@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.defaultContentType(MediaType.APPLICATION_JSON_UTF8);
}
}
java复制// 读取文件
List<String> lines = Files.readAllLines(Paths.get("data.txt"), StandardCharsets.UTF_8);
// 写入文件
Files.write(Paths.get("output.txt"), content.getBytes(StandardCharsets.UTF_8));
java复制CSVFormat format = CSVFormat.DEFAULT
.withHeader(HEADERS)
.withDelimiter(',')
.withQuote('"')
.withEscape('\\')
.withIgnoreEmptyLines()
.withRecordSeparator("\r\n");
乱码表现形式分析:
十六进制查看法:
java复制String str = "中文";
byte[] bytes = str.getBytes(StandardCharsets.UTF_8);
System.out.println(Arrays.toString(bytes)); // 输出字节数组
编码检测工具:
file -I filename (Mac/Linux)chardet (Python库)转换工具:
java复制String fixedStr = new String(badStr.getBytes("GBK"), "UTF-8");
项目文档明确要求:
代码审查重点:
跨环境测试:
边界测试:
UTF-8文件开头的BOM头可能导致问题:
java复制String content = FileUtils.readFileToString(file, StandardCharsets.UTF_8);
content = content.replace("\uFEFF", ""); // 移除BOM
推荐使用Apache Commons IO工具:
java复制String content = IOUtils.toString(inputStream, StandardCharsets.UTF_8);
IOUtils.write(content, outputStream, StandardCharsets.UTF_8);
注意这些系统属性:
案例1:日志文件乱码
xml复制<encoder charset="UTF-8">
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
案例2:接口返回乱码
java复制@RequestMapping(produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
编码转换开销:
内存占用:
bash复制JAVA_TOOL_OPTIONS="-Dfile.encoding=UTF-8"
bash复制export LANG=en_US.UTF-8
export LC_ALL=en_US.UTF-8
在Dockerfile中设置:
dockerfile复制ENV LANG C.UTF-8
ENV LC_ALL C.UTF-8
经过多年项目实践,我总结出一个原则:在编码问题上,显式优于隐式,统一优于多样。从项目开始就建立严格的编码规范,可以避免后期大量的兼容性问题。特别是在微服务架构下,各服务间的编码一致性更为重要。