问题描述
某日线上环境报警, 分析后发现是 java.lang.String.getBytes() 方法在不同环境上表现出来的结果是不一样的.
JDK 的 String.getBytes() 默认采用什么编码
看下 JDK 的代码不难发现:
先是靠 Charset.defaultCharset() 编码, 如果不行靠 ISO-8859-1 兜底, 再不行就直接应用退出了.
Charset.defaultCharset 的值取决于什么
看下 JDK 的代码不难发现先取 file.encoding 指定的编码, 不行就拿 UTF-8 兜底.
做实验验证两个环境
写个很简单的代码在环境上执行一下:
- public static void main(String[] args) {
- System.out.println(System.getProperty("file.encoding"));
- System.out.println(Charset.defaultCharset());
- }
结果发现 AB 两个环境都是:
- ANSI_X3.4-1968
- US-ASCII
这就有点让人郁闷了, 第一: 应该是 UTF-8 才对啊, 第二两个环境获取出来值的都一样, 那么上面的 String.getBytes() 在两个环境上的结果也应该是一样的啊, 应该将错就错啊.
查看两台机器的 locale
A 是:
- $ locale
- LANG=en_US.UTF-8
- LC_CTYPE=UTF-8
- LC_NUMERIC="en_US.UTF-8"
- LC_TIME="en_US.UTF-8"
- LC_COLLATE="en_US.UTF-8"
- LC_MONETARY="en_US.UTF-8"
- LC_MESSAGES="en_US.UTF-8"
- LC_PAPER="en_US.UTF-8"
- LC_NAME="en_US.UTF-8"
- LC_ADDRESS="en_US.UTF-8"
- LC_TELEPHONE="en_US.UTF-8"
- LC_MEASUREMENT="en_US.UTF-8"
- LC_IDENTIFICATION="en_US.UTF-8"
- LC_ALL=
B 是
- $ locale
- LANG=en_US.UTF-8
- LC_CTYPE=UTF-8
- LC_NUMERIC="en_US.UTF-8"
- LC_TIME="en_US.UTF-8"
- LC_COLLATE="en_US.UTF-8"
- LC_MONETARY="en_US.UTF-8"
- LC_MESSAGES="en_US.UTF-8"
- LC_PAPER="en_US.UTF-8"
- LC_NAME="en_US.UTF-8"
- LC_ADDRESS="en_US.UTF-8"
- LC_TELEPHONE="en_US.UTF-8"
- LC_MEASUREMENT="en_US.UTF-8"
- LC_IDENTIFICATION="en_US.UTF-8"
- LC_ALL=
按理说都是 UTF-8.
继续检查应用进程的环境变量, 确认看看有没有 - D 通过虚拟参数指定, 检查后, 结果是没有.
检查进程相关的环境变量, 检查后, 结果是没有.
cat /proc / 进程 id/environ | tr '\0' '\n'
用 locale charmap 命令确认 AB 两个环境:
得到的结果都是 ANSI_X3.4-1968
因为某个奇怪的原因在农神和我一起看这个问题的时候, 我用了 Mac 自带的终端工具连了跳板机, 跳板机再连到 AB 量环境. 上面我都是用的 iTerm2 连的跳板机, 然后再连的环境.
在我用 Mac 自带的终端工具连环境时发现用来验证的那段简单的代码输出结果不是 ANSI_X3.4-1968 了, 而是:
UTF-8 UTF-8
我恍然大悟了, 有问题那个环境, 确实是去年冬天我用 iTerm2 连接上然后手动重启的, 没问的那个应用是 ob3 系统部署的.
看来问题是出在 iTerm2 这个终端连接工具上了.
为 iTerm2 会导致远端环境编码不对
SSH 连接的时候开启 - v 选项
SSH -v xxx@yy.yyy.yyy.yy
发现 iTerm2 连接时是:
- debug1: Sending env LANG = en_US.UTF-8
- debug1: Sending env LC_CTYPE = UTF-8
发现 Mac 自带终端连接时是:
- debug1: Sending env LANG = zh_CN.UTF-8
- debug1: Sending env LC_CTYPE = zh_CN.UTF-8
差异就是在这, LC_CTYPE 送的值不一样, 导致覆盖操作系统 LC_CTYPE 的值不一样, 导致 locale charmap 不能识别, 导致操作系统采用默认编码 ANSI_X3.4-1968.
查到官方 issue 在这里, iTerm2 LC_CTYPE locale issue on SSH connections https://gitlab.com/gnachman/iterm2/issues/5478 , 缺陷列表中 @tsl0922 提到的版本新与我一致. MacOS Mojave,iTerm2 3.2.1.
为 iTerm2 发送了不一样的 LC_CTYPE, 因为在有问题的那个版本组合下:
- env |grep LC_CTYPE
- LC_CTYPE = UTF-8
升级到官方最新版本 3.2.7, 问题解决.
终端工具还能影响应用的编码, 之前完全没往这个方向考虑.
来源: http://www.bubuko.com/infodetail-2986311.html