ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

IDEA中文乱码全场景排查:从编辑器到Tomcat与数据库

IDEA中文乱码全场景排查:从编辑器到Tomcat与数据库 如果你是用IntelliJ IDEA写Java的老朋友几乎不可能没被“中文乱码”这四个字折磨过。第一次遇到是刚把项目从Windows旧同事手里接过来打开注释满屏号后来换成自己用Spring Boot写接口控制台里printf一行中文又成了一堆方框再后来部署到Tomcat 8日志、页面、请求参数全都在乱那种感觉就像所有编码环节同时跟你作对。这篇文章不跟你讲大道理直接按场景把IDEA中文乱码的常见套路拆开编辑器里的乱、控制台输出的乱、JavaWeb链路上的乱、数据库里的乱每一类怎么定位、怎么改我把实际用过的方案和踩过的坑都写在下面。1.1 乱码不是玄学先问三个问题处理过几十次乱码问题后我最大的感受是乱码一般不是“IDEA坏了”而是“字节还是那些字节解释方式换了”。同一个“博”字用UTF-8保存成了三个字节E5 8D 9A可如果你拿GBK去读它可能就被拼成一个你不认识的字反过来也一样。所以你第一步不是去网上搜“IDE破解”“重装大法”而是先问自己三个问题乱码出现在哪里——是代码编辑器里看到乱码还是控制台输出乱码还是浏览器页面、数据库里乱码这个乱码是“一打开就这样”还是“运行之后才出现”文件本身是用什么编码保存的你现在让IDEA用哪种编码去读这三个问题的答案基本决定了你该动哪里。编辑器里的乱码核心是项目和文件的编码设置控制台输出乱码核心是JVM运行时和终端解码不一致JavaWeb请求响应乱码核心是容器、过滤器、页面编码没对齐数据库乱码则是连接串和表结构字符集不统一。很多人乱改一通最后越搞越乱就是因为没分清这几种场景。我建议你拿到问题先截个图记录一下乱码发生的具体位置和触发操作再去对照后面对应的章节。我先给一个速览表方便你做第一轮判断乱码场景最可能的原因一句话定位方法编辑器里显示乱码文件保存编码和IDEA读取编码不一致看编辑器右下角当前编码换一下试试控制台println/printf乱码JVM输出编码与控制台解码编码不一致改VM options里的file.encodingTomcat日志乱码Tomcat的ConsoleHandler编码不对看logging.propertiesJSP/响应乱码pageEncoding或response编码没设对先看浏览器查看编码是什么请求参数乱码GET/POST链路任意一环不是UTF-8分别测GET参数和POST body数据库读写乱码JDBC连接串、表字符集、客户端不一致执行SHOW VARIABLES LIKE character_set%1.2 UTF-8和GBK一旦开始“打架”乱码就会出现很多人听到编码就头疼其实你只需要理解一个比喻编码就是字典。UTF-8是国际通用字典GBK是中国Windows老环境常用的本地字典。你用英文起草了一封信然后让一个只看中文字典的人来翻译他当然会翻出莫名其妙的句子。程序里也是这样文件里的字节流没有变变的是“拿着哪本字典去读”。UTF-8是变长编码英文字符占一个字节中文一般占三个字节GBK则用两个字节表示一个汉字。当一段用UTF-8编码的中文被当成GBK来读原本三个字节一组的划分错位就出现了这种替换字符反过来GBK文件被当成UTF-8读也会产生一堆乱码。另外还有一个隐蔽的小角色叫BOMByte Order Mark也就是UTF-8文件开头那几个特殊字节。很多记事本保存的UTF-8文件带BOMIDEA一般能识别但某些脚本、Properties文件、XML解析器会因为它立刻报错这也是“文件看着没问题一运行就乱”的常见来源。所以解决乱码的真正要义只有一句话让“文件保存编码、IDE读取编码、编译过程编码、运行环境编码、数据库编码”这五层保持一致。接下来我从编辑器开始一层一层往下拆。2. 编辑器与文件级中文乱码修复2.1 File Encodings面板才是项目编码的“总闸”如果你打开一个项目发现所有Java文件里的中文注释都成了乱码那多半是IDEA用了一种编码去读另一种编码的文件。此时最应该动的是Settings里的File Encodings而且千万不要只看一个下拉框因为这里其实有三个关键设置Global EncodingIDE全局的默认编码我一般保持UTF-8Project Encoding当前项目的标准编码这个通常也是UTF-8但如果你的老项目是GBK这里就应该跟着改成GBKDefault encoding for properties files专门管.properties文件的编码有时还会配合“Transparent native-to-ascii conversion”复选框把中文以ASCII转义形式显示。很多人只改了Project Encoding发现没效果其实是因为文件本身的历史编码和项目编码不是一回事。比如项目设置是UTF-8但某个.java文件是从外部拷贝来的GBK文件IDEA依然可能用UTF-8去读于是乱码。判断方法很简单打开乱码文件看IDEA右下角显示的是UTF-8还是GBK。如果显示的内容和文件真实编码不匹配直接点击右下角手动换到正确的编码重新加载往往马上就能看到正常中文。这里有一个非常重要的提醒当你把文件从GBK切换到UTF-8时如果使用的是“Convert”而不是“Reload”文件字节会被真正改写。操作前最好先确认文件已经备份或者你清楚地知道自己在干什么。我刚开始接触IDEA时就吃过一次亏一个老项目里几十个GBK文件我全选之后直接转换成了UTF-8结果所有文件里的中文注释全部变成了乱码最后只能靠本地历史找回。所以改编码之前先停下三秒想清楚是“换一种方式读”还是“把文件彻底转换”。2.2 单文件转码先“重新加载”再“转换”遇到单个文件乱码我建议按下面的顺序操作能最大程度避免误伤先点击IDEA右下角当前编码区域会弹出候选编码列表如果你的文件其实还是GBK只是被IDEA当成UTF-8读了那就选择GBK这时IDEA会用GBK重新解释字节中文大概率恢复如果你确认要把文件永久保存成UTF-8等到中文正确显示之后再在编码列表里选择“Convert to UTF-8”这样文件字节会被转成UTF-8并且后续保存都用UTF-8。这两个动作的区别看起来很细微但一个是“换字典读原著”另一个是“把原著翻译成另一门语言”。你要是跳过了第一步直接Convert本来就已经错位的字节可能会被再次错误转换乱上加乱。另外如果你在编码列表里看到类似“GBK (ISO-8859-1)”的选项说明系统在尝试用某种拉丁编码去映射中文这种转换出来的往往还是乱码别指望它能救回来。还有一种特殊情况文件里的中文只有一小部分乱码其他都正常。这种多半不是编码不一致而是文件被不同工具反复编辑过中间掺杂了少量异编码字符。这时候我会直接选中乱码区域看能否通过Find and Replace或者重新录入来解决如果乱码区域很大与其手动修补不如从版本控制系统里找到这份文件的正确版本重新拉取。我的经验是手工修编码残片性价比极低正确工具都比不上回到历史版本。2.3 从Git/Gitee拉下来的项目一打开就乱码从Gitee或者GitLab上克隆项目到本地打开后注释乱码这个问题在工作环境里经常遇到。大部分情况是仓库里的文件本身是GBK而你本地IDEA默认用UTF-8打开也有反向的仓库是UTF-8但你在Windows上装了某些工具把文件转成了GBK。想要判断到底是哪一种可以先把文件用“记事本”一类工具打开看看它的真实编码标记或者干脆看远程仓库里同事的编码约定。如果仓库明确是UTF-8那本地IDEA的项目编码设置为UTF-8即可如果仓库是老古董GBK项目我不建议强行把IDEA全局改成GBK而是把这个项目单独设置的Project Encoding改为GBK确保只有这个项目受影响。不过更推荐的做法是趁项目迁移的时候批量把GBK文件转成UTF-8。你可以在IDEA里用插件批量转换也可以写个脚本调用iconv来做。命令大概是下面这样find ./src -name *.java -exec iconv -f GBK -t UTF-8 {} -o {}.tmp \; -exec mv {}.tmp {} \;运行前一定要先备份或者提交当前状态因为这属于批量改写文件内容一旦出错就是一片红。我的习惯是先转换一两个文件确认中文正常、代码能编译再跑全量。另外如果团队里同时有Windows和macOS同事行尾符也会影响观感但不至于产生中文乱码。真正影响乱码的还是文件编码本身别让git config core.autocrlf这类行尾转换背锅。3. 控制台输出与printf中文乱码实战3.1 printf/System.out打印出的中文乱码你在IDEA里点运行代码里的System.out.println(中文)明明写的是中文控制台却输出涓枃或者???这是最经典的运行期乱码。核心原因在于Java进程在输出时用的是JVM默认字符集而IDEA控制台在接收时用的是自己的字符集。在中文Windows上老版本JDK默认字符集常常是GBK控制台如果按UTF-8解码就会乱码反过来也有。最简单的修法是给运行进程强制指定UTF-8编码。在你的Run/Debug配置里找到VM options加上-Dfile.encodingUTF-8如果还不行再加一个-Dconsole.encodingUTF-8这两个参数的意思是让JVM把文件读取和标准输出都统一到UTF-8。加完之后重跑一下main方法大多数printf中文乱码就能解决。如果你用的是JDK 18或更高版本Java官方已经把默认文件编码改成了UTF-8这种场景会少很多但老项目还是经常碰到。还有一种情况是你在IDEA里跑没问题一旦用命令行java -jar运行就乱码或者反过来。这多半是外部终端比如Windows cmd的代码页和程序输出编码不一致。你在cmd里先执行chcp 65001切到UTF-8代码页再用java -Dfile.encodingUTF-8 -jar app.jar启动一般就能恢复正常。我见过不少人反复折腾代码其实环境变量和代码页没有对齐改半天都是白费功夫。3.2 Tomcat与JavaWeb日志的中文乱码Tomcat 8中文乱码是一个非常高频的搜索点。很多人通过IDEA配置Tomcat启动项目日志里中文全乱而单独在IDEA里跑Java程序却正常。为什么因为Tomcat的日志组件java.util.logging在输出控制台时默认使用了平台的编码在中文Windows下就是GBK而IDEA控制台按UTF-8来读两边不对齐日志自然乱。我常用的修法按优先级排列在IDEA的Tomcat Run Configuration里找到VM options填入-Dfile.encodingUTF-8重启Tomcat修改Tomcat conf目录下的logging.properties找到下面这一行把它从GBK或空值改成UTF-8java.util.logging.ConsoleHandler.encoding UTF-8如果你是通过脚本独立启动Tomcat可以在catalina.batWindows或catalina.shLinux里设置CATALINA_OPTS加上-Dfile.encodingUTF-8。这里有个细节坑logging.properties本身是一个需要被Tomcat正确解析的配置如果你用Windows记事本保存成UTF-8并带了BOMTomcat启动时可能直接报错或者读不出第一行。存这个文件时最好保持ASCII/ANSI编码也就是只改那一行内容其他一律不动。我身边同事栽过好几次在这上面都是因为记事本画蛇添足。如果你部署在Linux服务器上Tomcat日志乱码通常和系统语言环境有关。可以检查locale是不是en_US.UTF-8一类如果不是在启动脚本开头加上export LANGen_US.UTF-8 export CATALINA_OPTS-Dfile.encodingUTF-8日志编码这种东西看似小事但线上排查问题时只要一条中文日志变成乱码你就得浪费小半天去猜原来的内容所以还是值得提前配好。3.3 Maven/Gradle编译和IDEA构建的编码对齐你可能遇到过这种怪事文件在IDEA里打开是正常中文运行程序也正常但用Maven打包的时候日志输出乱码或者打出来的包扔到服务器上中文就出问题。这种往往是源码编译阶段的编码没有固定下来。Maven默认使用project.build.sourceEncoding属性来确定源码和资源文件的编码如果你的pom.xml里没有显式指定它就依赖系统默认编码Windows和macOS很可能不同。建议在pom.xml的properties里写清楚properties project.build.sourceEncodingUTF-8/project.build.sourceEncoding project.reporting.outputEncodingUTF-8/project.reporting.outputEncoding /propertiesGradle项目则在build.gradle里配置tasks.withType(JavaCompile) { options.encoding UTF-8 }这么做的理由很简单编译环节是“文件编码”和“运行编码”之间的桥梁你不显式指定编译器和IDE就可能各自发挥。团队里只要有一台Windows机器编码习惯不对打出来的包就可能在别处乱码。我在刚开始带项目时总觉得“大家都用IDEA默认设置就行”后来发现“默认”在不同系统下含义完全不一样于是强制在构建脚本里固化编码乱码问题立刻少了一大半。4. JavaWeb与Tomcat中文乱码的全链路排查4.1 请求参数乱码GET和POST分开看JavaWeb项目的乱码往往不止一处最常见的就是请求参数。一个用户在页面上输入中文提交到后端数据库存进去就变成了问号或者后台接收后打印出来就是乱码。要定位这条链路必须把GET和POST分开看。GET请求的参数在URL里Tomcat默认会按URIEncoding去解码。从Tomcat 8开始默认的URIEncoding已经改成了UTF-8所以通常不会乱但如果你的server.xml里Connector配置被改成GBK或者某些老Tomcat版本用了默认ISO-8859-1那URL里的中文就会乱。稳妥做法是显式给Connector加上Connector port8080 protocolHTTP/1.1 connectionTimeout20000 redirectPort8443 URIEncodingUTF-8 /POST请求的参数在请求体里Tomcat会根据请求头里的Content-Type的charset来解码。如果客户端没有声明charset或者你的项目没有统一过滤器后端用request.getParameter拿到乱码是常有的事。最省事的方案是用一个CharacterEncodingFilter在容器读取参数之前就把request和response的编码设为UTF-8。Spring Boot项目甚至不用自己写只需要确保配置里没有关掉默认的编码过滤器传统Spring MVC项目可以在web.xml里配置Spring提供的CharacterEncodingFilter确保它是最先执行的过滤器。我不推荐只在某一个Controller里手动request.setCharacterEncoding(UTF-8)因为那样既麻烦又容易漏。正确做法是在链路最前面统一设置让所有请求都经过同一个编码入口。你要是接手老项目可以从“过滤器顺序 Tomcat URIEncoding 前端页面charset”三个点一起查基本能覆盖90%的请求乱码。4.2 响应输出乱码顺序不对什么都白搭请求参数乱码解决之后下一个容易踩坑的是响应乱码。明明Servlet里写了response.getWriter().write(中文)浏览器显示却是一堆乱码。很多人会立刻怀疑Tomcat或IDEA但真实原因往往只有一句话你设置响应编码的时序错了。对于Servlet/JSP体系标准的顺序是先调用response.setCharacterEncoding(UTF-8)再设置Content-Type最后才能getWriter()。如果你已经getWriter()之后再调setCharacterEncoding那这个设置对当前响应基本无效因为你拿到的Writer已经按旧编码创建了。更省事的写法是在页面或代码开头就指定response.setContentType(text/html; charsetUTF-8); response.setCharacterEncoding(UTF-8);如果你用了Spring MVC或Spring Boot还在用RequestMapping返回JSON那响应编码主要由HttpMessageConverter决定。老项目的Spring MVC返回中文乱码经常是StringHttpMessageConverter默认用了ISO-8859-1需要在配置里把它替换成UTF-8版本的Converter。Spring Boot 2.x之后这个默认值已经改成了UTF-8但如果你显式覆盖了消息转换器就要自己再指定一遍。浏览器端的对应检查也很重要如果你设置了UTF-8输出但浏览器强制用GBK解码那照样乱。好在现代浏览器基本都会尊重响应头里的charset只要服务端不搞混页面端的乱码基本不会出现。4.3 JSP页面与模板引擎的页面乱码JSP是我见过最容易被编码问题围攻的东西因为它同时牵扯文件保存编码、JSP编译器解码、响应编码三层。你在IDEA里打开JSP文件是正常的浏览器一访问就乱码问题很可能出在pageEncoding和contentType没有同时指定UTF-8。我建议每个JSP文件头部都写清楚% page languagejava contentTypetext/html; charsetUTF-8 pageEncodingUTF-8 %pageEncoding告诉JSP编译器“这个文件本身是什么编码”contentType告诉浏览器“这个响应是什么编码”。两者必须一致否则就会出现文件和响应不一致的乱码。纯HTML页面的情况类似记得在head里写meta charsetUTF-8并且确保HTML文件本身以UTF-8保存。如果你用Thymeleaf、FreeMarker这类模板引擎页面乱码的原因通常是模板引擎读取模板文件时用错了默认编码。Thymeleaf在Spring Boot中默认读取UTF-8但老式配置里可能因为spring.thymeleaf.encoding没设置而回退到平台默认编码。我建议在application.properties里显式写一行spring.thymeleaf.encodingUTF-8很多人觉得Spring Boot“开箱即用”就不会乱码实际上只要你手动覆盖过某些配置比如模板解析器或者消息转换器默认值就可能失效。所以排查页面乱码时别只顾着看HTML里的meta标签还要把服务端读取模板文件的编码一并检查。5. 数据库读写中文乱码与连接串配置5.1 JDBC连接串里的三个关键参数数据库中文乱码是最后一道大坑。程序运行正常前端也正常但数据存到MySQL后再读出来就是???这种问题九成出在数据库连接串。JDBC连接串里以前习惯写到URL中的useUnicode和characterEncoding往往被忽略或者被某个同事手滑改成GBK。我建议的标准连接串是这样的jdbc:mysql://localhost:3306/yourdb?useUnicodetruecharacterEncodingUTF-8serverTimezoneAsia/Shanghai这三个参数的意思分别是启用Unicode支持、告诉驱动采用UTF-8编码传输、设置时区避免时间类型报错。MySQL Connector/J 8.0之后的版本里serverTimezone基本是必填否则连接时可能会因为时区差异直接报错而characterEncoding如果不写驱动会拿服务器默认编码来传输和你的Java程序设置一旦不一致中文就很容易变问号。还要注意一个细节如果你是在application.properties或者jdbc.properties这类资源文件里写连接串符号有时候需要写成amp;特别是当连接串被放进XML配置的时候。我在一个项目里见过同事把application.properties里的连接串直接粘到pom.xml里做Maven profile配置结果被XML解析器当成特殊符号导致整个配置失效。这种错误很隐蔽排查时可能会绕很远的弯路。5.2 数据库、表和客户端三层字符集保持一致即使连接串设置正确如果数据库本身或某张表的字符集不是UTF-8写入中文还是会出问题。MySQL最常被诟病的一点就是安装时默认字符集可能是latin1你建库的时候如果不显式指定utf8mb4后续查起来非常麻烦。utf8mb4和utf8的区别在于前者能存emoji这类四字节字符所以我建议统一使用utf8mb4。登陆数据库执行SHOW VARIABLES LIKE character_set%;重点关注character_set_database、character_set_connection、character_set_client几个值。如果它们不是utf8mb4你可以在建库时指定CREATE DATABASE yourdb DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;已经存在的库可以用ALTER DATABASE和ALTER TABLE来调整。表结构层面的字符集同样重要因为如果你连接串是UTF-8但表字段的collation是latin1_swedish_ci驱动写入时可能发生转换问题。我的建议是数据库连接串、数据库默认字符集、表字符集、Java程序编码四个地方全部用UTF-8/utf8mb4不要混搭。排查数据库乱码时我通常先写个简单的JDBC测试直接从Java里插入一条中文再查询出来如果这一步都乱那就一定是连接串或数据库字符集的问题如果这一步正常才去考虑上层业务逻辑或前端的问题。5.3 从CSV/Excel导入时为什么又乱码另一个容易忽略的场景是数据导入。有时候你在IDEA里写导入工具读取一个CSV文件file里的中文明明是正常的用FileReader读出来却乱了。很多人以为FileReader默认就是UTF-8其实它的默认编码跟随JVM的系统默认编码在中文Windows上就是GBK。如果你用BufferedReader包装读取最好显式指定CharsetBufferedReader reader Files.newBufferedReader( Paths.get(data.csv), Charset.forName(UTF-8));如果CSV文件本身是GBK就要用Charset.forName(GBK)读否则同理也会乱码。我在写导入功能时会要求文件格式在文档里写明“本工具只支持UTF-8编码CSV”并在代码里做编码检测或试读如果检测到非法字符就立即报错而不是把乱数据写进数据库。这种“前端防御”看起来多余实际上能帮你省掉大量脏数据清理工作。6. 乱码问题速查表与避坑心得6.1 一份可直接抄的“乱码速查表”我把这两年处理IDEA中文乱码的经验整理成了一张表遇到问题可以直接对着找现象首选修复方案备选方案项目文件注释乱码File Encodings里把Project Encoding切换到正确编码用右下角编码按钮重新“Reload”单个文件乱码右下角编码切换到真实编码显示确认无误后执行“Convert”控制台println乱码运行配置VM options加-Dfile.encodingUTF-8再加-Dconsole.encodingUTF-8Tomcat日志乱码改logging.properties中ConsoleHandler编码Tomcat Run Config加VM optionsMaven打包乱码pom.xml设置sourceEncodingUTF-8检查IDEA Build Tool编码设置GET参数乱码server.xml的Connector设置URIEncodingUTF-8前端对URL进行编码POST参数乱码配置CharacterEncodingFilter检查请求的Content-Type charset响应输出乱码setCharacterEncoding必须在getWriter之前检查StringHttpMessageConverterJSP页面乱码页面同时设置pageEncoding和contentType检查JSP文件保存编码数据库写入问号JDBC连接串加characterEncodingUTF-8检查库和表的utf8mb4字符集CSV导入乱码用Files.newBufferedReader显式指定UTF-8检查CSV文件真实编码这张表不能替代真正的排查但大多数场景已经覆盖了。我每次给新同事讲乱码问题都是先让他们拿着这张表去现场对照而不是直接给代码。因为只有自己定位过一遍后面遇到类似问题才记得住。6.2 别乱按“Convert”先看本地历史关于编码操作我特别想强调一个教训IDEA右下角的编码菜单里“Convert”和“Reload”是完全不同的动作但界面看起来只差一点点。我见过有人为了看乱码文件不小心选了“Convert”导致整个文件所有中文被重新编码而且保存之后之前的内容再也回不来。如果你已经误操作了千万不要慌IDEA有本地历史右键文件或目录选择Local History - Show History能找到最近修改前的版本。这个功能救过我很多次比依赖Git提交更即时。另外一个建议是大量文件需要转码时不要用“全选所有文件然后Convert”这种粗暴方式。更好的做法是先转一两个文件编译跑通再批量处理。批量转换前如果当前项目在Git管理下就先把当前状态提交一次确保万一转乱了还能随时回滚。这看起来是个笨办法但真到几十个文件同时乱码时你就知道这个备份有多值钱。6.3 我最后想说的几个习惯把乱码问题从根上掐掉靠的不是“出问题后修”而是“一开始就统一”。我现在的习惯是新建项目第一件事就去File Encodings里把三个编码全部设为UTF-8Maven项目在pom.xml里固化sourceEncoding数据库连接串里写死characterEncodingUTF-8模板文件和静态资源统一UTF-8保存。团队协作时我会在项目README里写清楚“本项目所有源文件、构建、运行、数据库统一UTF-8”让后来接手的人不至于靠猜。这种统一约定比任何技巧都管用因为乱码的本质就是不一致你让所有环节都站在同一种编码上大部分问题自然会消失。如果你实在遇到了这里没写到的乱码场景我的经验是先分清是“保存编码”错还是“读取编码”错再看“运行环境编码”是否独立。只要沿着“文件→构建→运行→容器→数据库→客户端展示”这条链路逐层检查总能找到那一层掉链子。最后再说一句与其在网上下载来路不明的IDE破解包不如直接用官方社区版正版社区版完全够用而且不会因为被修改过的内置环境参数引入奇怪的乱码问题。我做乱码排查这些年见过太多被第三方“优化版”环境坑的例子了。
返回列表