Java场景下UTF-8为何无法正确解析进程输入流中的特殊字符
提问内容
我之前的提问被标记为和「Which encoding does Process.getInputStream() use?」重复,但这不是我要问的核心问题。我的第二个示例里用UTF-8可以正常解析特殊字符,但如果特殊字符来自进程输入流,UTF-8就没法正常解析了。请问出现这个问题的原因是什么?是不是意味着ISO_8859_1是唯一可选的编码?
我正在开发一款可以在运行时获取Azure Key Vault密钥的插件,目前遇到了编码问题:我存储了包含特殊字符ç的字符串HrIaMFBc78!?%$timodagetwiçç99,但用下面的程序读取时,特殊字符ç无法正常解析:
package com.buildingblocks.azure.cli; import java.io.*; import java.nio.charset.StandardCharsets; public class Test { static String decodeText(String command) throws IOException, InterruptedException { Process p; StringBuilder output = new StringBuilder(); p = Runtime.getRuntime().exec("cmd.exe /c \"" + command + "\""); p.waitFor(); InputStream stream; if (p.exitValue() != 0) { stream = p.getErrorStream(); } else { stream = p.getInputStream(); } BufferedReader reader = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8)); String line = ""; while ((line = reader.readLine()) != null) { output.append(line + "\n"); } return output.toString(); } public static void main(String[] arg) throws IOException, InterruptedException { System.out.println(decodeText("az keyvault secret show --name \"test-password\" --vault-name \"test-keyvault\"")); } }
程序输出为:"value": "HrIaMFBc78!?%$timodagetwi��99"
如果我用下面的程序解析该字符串,特殊字符ç可以正常解析:
package com.buildingblocks.azure.cli; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStreamReader; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; public class Test { static String decodeText(String input, String encoding) throws IOException { return new BufferedReader( new InputStreamReader( new ByteArrayInputStream(input.getBytes()), Charset.forName(encoding))) .readLine(); } public static void main(String[] arg) throws IOException { System.out.println(decodeText("HrIaMFBc78!?%$timodagetwiçç99", StandardCharsets.UTF_8.toString())); } }
两段代码用的BufferedReader配置完全一致,但解析进程输出的代码运行失败,请问是什么原因导致的?
问题解答
- 两段代码的核心差异
第二段测试代码是直接将Java内存中已按UTF-8存储的字符串转换为字节流,再用UTF-8解码,编码完全匹配所以不会出现乱码。第一段代码读取的是cmd.exe执行az命令输出的原始字节流,这个流的编码并非UTF-8,编码不匹配就会出现�替换字符。 - 进程输出流编码不匹配的原因
Windows系统下cmd.exe默认使用系统OEM代码页作为输出编码,比如简体中文系统默认代码页为936(GBK),西欧语言系统默认代码页为850/1252,az cli会自动适配当前控制台的编码输出内容,因此你拿到的输出流字节是对应系统代码页的编码,直接用UTF-8解码自然会出错。 - 关于
ISO_8859_1是否是唯一选择
答案是否定的,你可以根据场景选择两种解决方案:- 适配当前系统默认编码:直接使用系统默认编码解码流即可,不需要硬编码为UTF-8,代码修改为
new InputStreamReader(stream, Charset.defaultCharset())即可,也可以手动查询当前系统代码页指定对应编码,比如西欧系统可以使用StandardCharsets.ISO_8859_1或者Charset.forName("Windows-1252")。 - 强制全链路使用UTF-8输出:修改执行的命令为
cmd.exe /c "chcp 65001 > nul && " + command + "",chcp 65001是将控制台代码页切换为UTF-8,> nul是屏蔽代码页切换的输出内容,之后az cli就会输出UTF-8编码的内容,你再用UTF-8解码就可以正常解析特殊字符。
- 适配当前系统默认编码:直接使用系统默认编码解码流即可,不需要硬编码为UTF-8,代码修改为
- 额外优化提示
你当前的代码逻辑是先调用p.waitFor()等待进程结束再读取流,存在性能问题甚至死锁风险:如果子进程输出内容较多填满了操作系统的缓冲区,子进程会阻塞等待缓冲区被读取,此时你的主进程在等待子进程退出,就会出现永久卡住的情况。建议修改逻辑为:启动进程后立即启动独立线程分别读取标准输出流和错误流,再调用waitFor()等待进程结束。
内容的提问来源于stack exchange,提问作者Coding_Rabbit
相关产品推荐
相关产品推荐

