最近用java开发企业微信会话存储时,碰到字符串编码问题,起因是因为开发时采用的编码环境和数据库编码都是UTF-8,在Linux环境下没有问题,但在windows环境下部署时,由于windows默认采用的是GBK编码,导致在解析中文会话时出现乱码。
找到原因后,以为只要将GBK编码的字符串直接转到UTF-8即可,但在网上找了一圈和编码实现后发现转码后中文出现数据丢失的问题。无奈又重新在网上找了一圈,深入了解GBK编码和UTF-8编码的编码格式才发现,原来在进行GBK往UTF-8编码时一定会出现数据丢失的问题,究其原因,就在于其编码格式的特点导致。
gbk的中文编码是一个汉字用【2】个字节表示,例如汉字“内部”的gbk编码16进制的显示为c4 da b2 bf
utf-8的中文编码是一个汉字用【3】个字节表示,例如汉字“内部”的utf-8编码16进制的显示为e5 86 85 e9 83 a8
很显然,gbk是无法直接转换成utf-8,少字节变为多字节,谁知道缺少的字节是什么啊?!
那问题来了,能解决吗?
经过一番调研,解决方案有,但目前来说都不是最完美的。
第一种是采用有损转换
- 首先将gbk字符串getBytes()得到两个原始字节,转换成二进制字符流,共16位。
- 根据UTF-8的汉字编码规则,首字节以1110开头,次字节以10开头,第3字节以10开头。在原始的2进制字符串中插入标志位。最终的长度从16--->16+4+2+2=24。
- 转换完成,实际情况需要考虑更多因素,例如字符串是汉字和数字的混合体,需要识别处理数字。
1 public static String getUTF8StringFromGBKString(String gbkStr) {
2 try {
3 return new String(getUTF8BytesFromGBKString(gbkStr), "UTF-8");
4 } catch (UnsupportedEncodingException e) {
5 throw new InternalError();
6 }
7 }
8
9 public static byte[] getUTF8BytesFromGBKString(String gbkStr) {
10 int n = gbkStr.length();
11 byte[] utfBytes = new byte[3 * n];
12 int k = 0;
13 for (int i = 0; i < n; i++) {
14 int m = gbkStr.charAt(i);
15 if (m < 128 && m >= 0) {
16 utfBytes[k++] = (byte) m;
17 continue;
18 }
19 utfBytes[k++] = (byte) (0xe0 | (m >> 12));
20 utfBytes[k++] = (byte) (0x80 | ((m >> 6) & 0x3f));
21 utfBytes[k++] = (byte) (0x80 | (m & 0x3f));
22 }
23 if (k < utfBytes.length) {
24 byte[] tmp = new byte[k];
25 System.arraycopy(utfBytes, 0, tmp, 0, k);
26 return tmp;
27 }
28 return utfBytes;
29 }
第二种是采用最原始的做法,字符映射表,将GBK对应的字符与UTF-8做对应。目前在github上有一位大神用C++实现了一版,具体查看链接github.com/DavidLiRemi…,但该种方法依旧存在问题,因为UTF-8的字符集是远大于GBK的,导致UTF-8中的一些字符在GBK中没有对应的,出现少部分字符丢失。
第三种是直接转化环境,将运行环境的编码格式调成一致。在Linux环境下,默认都是UTF-8的环境,这时候只要保证代码和数据库表的字符集格式一致即可,但切换到windows环境下时,由于windows下默认是GBK环境,这个时候有两种解决思路:
- 一是将代码和数据库编码都调整正GBK环境,但这种方式要求你的代码维护要做好Linux和windows的环境识别和切换
- 二是将windows的java运行环境切换成UTF-8的,这里总结有几种实现方式:
- 针对jar包运行的程序,可在虚拟机参数上增加 -Dfile.encoding=utf-8
- 针对日志乱码的问题,可以修改spring中logback的参数,将下面参数去掉
<charset>utf8</charset> - 是在cmd控制台中通过chcp命令修改代码环境,chcp 936(系统默认)切换成中文GB2312、chcp 65001 切换成UTF-8环境