Java實(shí)戰(zhàn)之字符編碼問題的幾種解決方案

前言
最近在做一個(gè) Java 項(xiàng)目的時(shí)候,遇到了一個(gè)讓人頭疼的問題:在 Windows 上開發(fā)的時(shí)候,中文字符顯示正常,但部署到 Linux 服務(wù)器上就變成亂碼了。剛開始以為是數(shù)據(jù)庫的問題,檢查了數(shù)據(jù)庫字符集也沒問題。后來才發(fā)現(xiàn),原來是不同系統(tǒng)的默認(rèn)編碼不一致導(dǎo)致的。
相信很多開發(fā)者都遇到過類似的問題:代碼里寫的中文,在不同環(huán)境下顯示不一樣;從數(shù)據(jù)庫讀取的數(shù)據(jù),有時(shí)候是亂碼;日志文件里的中文顯示不正常。這些問題雖然看起來簡單,但如果不了解字符編碼的原理,可能會折騰很久。今天我們就來聊聊字符編碼問題的原因和解決方案。
問題背景
字符編碼問題是一個(gè)很常見但又容易被忽視的問題。不同的系統(tǒng)、不同的環(huán)境,默認(rèn)的字符編碼可能不一樣,這就導(dǎo)致了各種亂碼問題。
為什么會出現(xiàn)編碼問題
現(xiàn)在的計(jì)算機(jī)系統(tǒng),底層都是使用二進(jìn)制來存儲數(shù)據(jù)的。但我們在屏幕上看到的文字,比如中文、英文、日文等,都是字符。要把字符轉(zhuǎn)換成二進(jìn)制存儲,就需要用到字符編碼。
常見的字符編碼:
- ASCII:最早的字符編碼,只能表示 128 個(gè)字符,主要是英文字母、數(shù)字和一些符號
- GBK/GB2312:中文編碼,Windows 系統(tǒng)默認(rèn)使用
- UTF-8:現(xiàn)在最流行的編碼,可以表示世界上所有的字符,跨平臺兼容性好
問題根源:
不同系統(tǒng)默認(rèn)的字符編碼不一樣:
- Windows 系統(tǒng)默認(rèn)使用 GBK 或 GB2312
- Linux 系統(tǒng)默認(rèn)使用 UTF-8
- Mac 系統(tǒng)默認(rèn)使用 UTF-8
如果你的代碼在 Windows 上開發(fā)(默認(rèn) GBK),但部署到 Linux 服務(wù)器上(默認(rèn) UTF-8),就可能出現(xiàn)亂碼問題。
常見的亂碼場景
在實(shí)際開發(fā)中,我們經(jīng)常會遇到這些亂碼場景:
場景一:文件讀取亂碼
從文件讀取數(shù)據(jù)時(shí),如果文件的編碼和讀取時(shí)使用的編碼不一致,就會出現(xiàn)亂碼。比如文件是 UTF-8 編碼的,但用 GBK 編碼去讀取,中文就會變成亂碼。
場景二:數(shù)據(jù)庫存儲亂碼
數(shù)據(jù)庫的字符集設(shè)置不對,或者連接數(shù)據(jù)庫時(shí)沒有指定正確的字符集,存儲和讀取的數(shù)據(jù)就可能出現(xiàn)亂碼。
場景三:日志輸出亂碼
程序輸出的日志,如果控制臺的編碼設(shè)置不對,或者日志文件的編碼不對,中文日志就會顯示成亂碼。
場景四:網(wǎng)絡(luò)傳輸亂碼
不同系統(tǒng)之間通過網(wǎng)絡(luò)傳輸數(shù)據(jù)時(shí),如果編碼不一致,接收到的數(shù)據(jù)可能就是亂碼。
解決方案一:統(tǒng)一使用 UTF-8
解決字符編碼問題最根本的方法,就是統(tǒng)一使用 UTF-8 編碼。UTF-8 是目前最流行的字符編碼,幾乎所有的現(xiàn)代系統(tǒng)都支持,而且可以表示世界上所有的字符。
為什么選擇 UTF-8
UTF-8 有很多優(yōu)點(diǎn):
- 兼容性好:幾乎所有的系統(tǒng)、瀏覽器、數(shù)據(jù)庫都支持 UTF-8
- 國際化支持:可以表示世界上所有的字符,包括中文、日文、韓文、阿拉伯文等
- 向后兼容:對于 ASCII 字符,UTF-8 編碼和 ASCII 編碼完全一樣
- 變長編碼:英文字符只占 1 個(gè)字節(jié),中文字符占 3 個(gè)字節(jié),比較節(jié)省空間
如何在項(xiàng)目中統(tǒng)一使用 UTF-8
代碼文件編碼:
確保所有的源代碼文件都使用 UTF-8 編碼保存。在 IDE 中,可以設(shè)置默認(rèn)的文件編碼:
- IntelliJ IDEA:File → Settings → Editor → File Encodings,將 Project Encoding 和 Default encoding for properties files 都設(shè)置為 UTF-8
- Eclipse:Window → Preferences → General → Workspace,將 Text file encoding 設(shè)置為 UTF-8
- VS Code:在設(shè)置中搜索 “files.encoding”,設(shè)置為 UTF-8
配置文件編碼:
配置文件(如 properties、xml、json 等)也要使用 UTF-8 編碼。特別是 properties 文件,如果包含中文,必須使用 UTF-8 編碼,否則會出現(xiàn)亂碼。
資源文件編碼:
資源文件(如國際化文件、模板文件等)也要使用 UTF-8 編碼。
解決方案二:設(shè)置 JVM 參數(shù)
在 Java 項(xiàng)目中,JVM 的默認(rèn)字符編碼可能會影響程序的運(yùn)行。如果 JVM 的默認(rèn)編碼不是 UTF-8,可能會導(dǎo)致文件讀寫、網(wǎng)絡(luò)傳輸?shù)炔僮鞒霈F(xiàn)亂碼。
設(shè)置 JVM 參數(shù)
在啟動(dòng) Java 程序時(shí),可以通過 JVM 參數(shù)來設(shè)置字符編碼:
java -Dfile.encoding=UTF-8 -jar your-app.jar
這個(gè)參數(shù)會告訴 JVM,文件系統(tǒng)的默認(rèn)編碼是 UTF-8。
在 IDE 中設(shè)置
如果你在 IDE 中運(yùn)行程序,也需要設(shè)置 JVM 參數(shù):
IntelliJ IDEA:
- 點(diǎn)擊 Run → Edit Configurations
- 選擇你的運(yùn)行配置
- 在 VM options 中輸入:
-Dfile.encoding=UTF-8 - 點(diǎn)擊 Apply 和 OK
Eclipse:
- 右鍵項(xiàng)目 → Run As → Run Configurations
- 選擇你的運(yùn)行配置
- 在 Arguments 標(biāo)簽頁的 VM arguments 中輸入:
-Dfile.encoding=UTF-8 - 點(diǎn)擊 Apply 和 Run
在代碼中設(shè)置
除了 JVM 參數(shù),也可以在代碼中設(shè)置系統(tǒng)屬性:
System.setProperty("file.encoding", "UTF-8");
但這種方式不推薦,因?yàn)橛行┐a可能在設(shè)置之前就已經(jīng)讀取了系統(tǒng)屬性。
驗(yàn)證編碼設(shè)置
可以通過代碼來驗(yàn)證當(dāng)前的編碼設(shè)置:
System.out.println("默認(rèn)字符編碼: " + System.getProperty("file.encoding"));
System.out.println("控制臺編碼: " + System.getProperty("console.encoding"));
如果輸出不是 UTF-8,說明設(shè)置沒有生效。
解決方案三:數(shù)據(jù)庫字符集設(shè)置
數(shù)據(jù)庫的字符集設(shè)置也很重要,如果數(shù)據(jù)庫的字符集不對,存儲和讀取的數(shù)據(jù)就可能出現(xiàn)亂碼。
MySQL 字符集設(shè)置
創(chuàng)建數(shù)據(jù)庫時(shí)設(shè)置字符集:
CREATE DATABASE mydb DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci;
utf8mb4 是 MySQL 中真正的 UTF-8 編碼,可以存儲所有的 Unicode 字符,包括 emoji 表情。而 MySQL 的 utf8 實(shí)際上只支持最多 3 字節(jié)的字符,不支持 emoji。
創(chuàng)建表時(shí)設(shè)置字符集:
CREATE TABLE users (
id INT PRIMARY KEY,
name VARCHAR(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
修改現(xiàn)有數(shù)據(jù)庫字符集:
如果數(shù)據(jù)庫已經(jīng)創(chuàng)建,可以修改字符集:
ALTER DATABASE mydb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; ALTER TABLE users CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
連接數(shù)據(jù)庫時(shí)設(shè)置字符集
在連接數(shù)據(jù)庫時(shí),也要指定字符集:
JDBC 連接字符串:
String url = "jdbc:mysql://localhost:3306/mydb?useUnicode=true&characterEncoding=utf8&useSSL=false";
或者使用更完整的參數(shù):
String url = "jdbc:mysql://localhost:3306/mydb?useUnicode=true&characterEncoding=utf8mb4&useSSL=false&serverTimezone=UTC";
Spring Boot 配置:
在 application.properties 或 application.yml 中配置:
spring.datasource.url=jdbc:mysql://localhost:3306/mydb?useUnicode=true&characterEncoding=utf8mb4&useSSL=false spring.datasource.username=root spring.datasource.password=password
或者:
spring:
datasource:
url: jdbc:mysql://localhost:3306/mydb?useUnicode=true&characterEncoding=utf8mb4&useSSL=false
username: root
password: password
其他數(shù)據(jù)庫的字符集設(shè)置
PostgreSQL:
PostgreSQL 默認(rèn)使用 UTF-8 編碼,創(chuàng)建數(shù)據(jù)庫時(shí):
CREATE DATABASE mydb WITH ENCODING 'UTF8';
Oracle:
Oracle 數(shù)據(jù)庫的字符集在創(chuàng)建數(shù)據(jù)庫時(shí)設(shè)置,之后很難修改。建議在創(chuàng)建數(shù)據(jù)庫時(shí)就選擇 UTF-8 相關(guān)的字符集,如 AL32UTF8。
實(shí)際應(yīng)用場景
讓我們看看幾個(gè)實(shí)際應(yīng)用場景,了解如何在實(shí)際項(xiàng)目中應(yīng)用這些解決方案:
場景一:Web 應(yīng)用開發(fā)
在 Web 應(yīng)用中,需要處理前端的請求和響應(yīng):
設(shè)置請求和響應(yīng)編碼:
在 Spring MVC 中,可以配置字符編碼過濾器:
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Bean
public CharacterEncodingFilter characterEncodingFilter() {
CharacterEncodingFilter filter = new CharacterEncodingFilter();
filter.setEncoding("UTF-8");
filter.setForceEncoding(true);
return filter;
}
}
或者在 web.xml 中配置:
<filter>
<filter-name>characterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
場景二:文件讀寫
在讀寫文件時(shí),要明確指定編碼:
讀取文件:
// 使用 UTF-8 編碼讀取文件
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(new FileInputStream("file.txt"), StandardCharsets.UTF_8))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
}
寫入文件:
// 使用 UTF-8 編碼寫入文件
try (BufferedWriter writer = new BufferedWriter(
new OutputStreamWriter(new FileOutputStream("file.txt"), StandardCharsets.UTF_8))) {
writer.write("中文內(nèi)容");
}
場景三:日志輸出
在輸出日志時(shí),要確保日志文件的編碼正確:
Logback 配置:
<configuration>
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>app.log</file>
<encoder>
<charset>UTF-8</charset>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
</configuration>
Log4j2 配置:
<Configuration>
<Appenders>
<File name="File" fileName="app.log">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss} [%t] %-5level %logger{36} - %msg%n" charset="UTF-8"/>
</File>
</Appenders>
</Configuration>
最佳實(shí)踐建議
在實(shí)際項(xiàng)目中,為了避免字符編碼問題,建議遵循以下最佳實(shí)踐:
統(tǒng)一編碼規(guī)范
- 所有代碼文件使用 UTF-8 編碼:包括 Java 源文件、配置文件、資源文件等
- 所有數(shù)據(jù)庫使用 UTF-8 字符集:MySQL 使用 utf8mb4,PostgreSQL 使用 UTF8
- 所有網(wǎng)絡(luò)傳輸使用 UTF-8 編碼:HTTP 請求和響應(yīng)、消息隊(duì)列等
- 所有日志文件使用 UTF-8 編碼:確保日志中的中文能正常顯示
明確指定編碼
在代碼中,凡是涉及字符編碼的地方,都要明確指定 UTF-8,不要依賴系統(tǒng)默認(rèn)編碼:
// 好的做法:明確指定編碼 String content = new String(bytes, StandardCharsets.UTF_8); // 不好的做法:依賴默認(rèn)編碼 String content = new String(bytes); // 可能在不同環(huán)境下表現(xiàn)不一致
驗(yàn)證編碼設(shè)置
在項(xiàng)目啟動(dòng)時(shí),可以輸出當(dāng)前的編碼設(shè)置,方便排查問題:
@PostConstruct
public void checkEncoding() {
System.out.println("file.encoding: " + System.getProperty("file.encoding"));
System.out.println("Default Charset: " + Charset.defaultCharset());
}
測試不同環(huán)境
在部署到不同環(huán)境之前,要測試字符編碼是否正常:
- 在 Windows 開發(fā)環(huán)境測試
- 在 Linux 測試環(huán)境測試
- 在 Linux 生產(chǎn)環(huán)境測試
確保在所有環(huán)境下,中文字符都能正常顯示。
總結(jié)
字符編碼問題雖然看起來簡單,但在實(shí)際項(xiàng)目中卻經(jīng)常遇到。解決這類問題的關(guān)鍵是統(tǒng)一使用 UTF-8 編碼,并在所有可能涉及編碼的地方明確指定。
關(guān)鍵點(diǎn)總結(jié):
- 統(tǒng)一使用 UTF-8:所有文件、數(shù)據(jù)庫、網(wǎng)絡(luò)傳輸都使用 UTF-8 編碼
- 設(shè)置 JVM 參數(shù):通過
-Dfile.encoding=UTF-8設(shè)置 JVM 默認(rèn)編碼 - 數(shù)據(jù)庫字符集:數(shù)據(jù)庫和表都使用 UTF-8 字符集,連接時(shí)也要指定字符集
- 明確指定編碼:在代碼中,凡是涉及編碼的地方都要明確指定,不要依賴默認(rèn)值
- 驗(yàn)證和測試:在不同環(huán)境下測試,確保編碼設(shè)置正確
最佳實(shí)踐:
- 項(xiàng)目開始時(shí)就統(tǒng)一編碼規(guī)范
- 在 IDE 中設(shè)置默認(rèn)編碼為 UTF-8
- 在構(gòu)建腳本中設(shè)置 JVM 參數(shù)
- 數(shù)據(jù)庫創(chuàng)建時(shí)就使用 UTF-8 字符集
- 代碼中明確指定編碼,不要依賴默認(rèn)值
到此這篇關(guān)于Java實(shí)戰(zhàn)之字符編碼問題的幾種解決方案的文章就介紹到這了,更多相關(guān)Java字符編碼問題內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java中枚舉的實(shí)現(xiàn)與應(yīng)用詳解
這篇文章主要介紹了Java中枚舉的實(shí)現(xiàn)與應(yīng)用詳解,EnumTest中還有一個(gè)VALUES數(shù)組,里面存儲著所有的枚舉實(shí)例,調(diào)用values方法時(shí)返回VALUES數(shù)組的clone,需要的朋友可以參考下2023-12-12
SpringBoot?項(xiàng)目的創(chuàng)建與啟動(dòng)步驟詳解
這篇文章主要介紹了SpringBoot?項(xiàng)目的創(chuàng)建與啟動(dòng),本文分步驟給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-03-03
Java異常簡介和架構(gòu)_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
這篇文章主要分享了Java異常簡介和架構(gòu),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-06-06
springmvc字符編碼過濾器CharacterEncodingFilter的使用
這篇文章主要介紹了springmvc字符編碼過濾器CharacterEncodingFilter的使用,具有很好的參考價(jià)值,希望對大家有所幫助。2021-08-08
Java中BufferedReader與BufferedWriter類的使用示例
BufferedReader與BufferedWriter分別繼承于Reader和Writer類,分別為字符的讀取和寫入添加緩沖功能,這里我們就來看一下Java中BufferedReader與BufferedWriter類的使用示例:2016-06-06
Java虛擬機(jī)JVM之server模式與client模式的區(qū)別
這篇文章主要介紹了Java虛擬機(jī)JVM的client模式和Server模式兩者的區(qū)別和聯(lián)系2017-12-12

