UTF8 编码识别

背景

早些年,java应用的开发环境是windows下使用eclipse。eclipse默认工程中文编码是GBK,这就为以后的业务埋下了隐患。

在大型native客户端开发中,模块按照功能/页面划分解耦。要解除相互之间的符号依赖(编译期间)和业务依赖(运行期间),可以使用”路由中心”,借鉴pc url思想来描述native页面。

恰好,我负责的搜索页面,native url中带有中文参数就牵涉这块编码问题了。

问题

由于业务上后端应用一直是GBK编码,所以搭建起来的一些运营系统对外投放的h5页面数据上中文是使用GBK的。这些上游页面使用url跳转会被”路由中心”拦截到我的页面,并且会将url中的params参数一一对应的填充到我的页面属性中,其中的中文参数会使用GBK解码。

随着开发环境的升级差异和前端同学推广UTF8的使用等,业务上上游页面渐渐出现了使用UTF8编码的情况。我们尝试通过约定来避免线上业务使用乱码的问题,比如:

  1. 强制要求上游业务必须使用GBK编码
  2. url中携带_input_charset参数表示是哪种编码方式(_input_charset=gbk/_input_charset=utf8)

但迭代下来,仍然线上会出现乱码问题 (UTF8编码使用GBK解码)。

主要原因是,历史上搭建的运营系统使用GBK编码,产生的上游页面并不会按照约定2来执行。而新系统正在积极的使用UTF8编码不按照约定1。在可预见的未来,UTF8应该会普及开来。

所以,会了应对乱码问题。客户端需要编码识别能力。

技术方案

我们先确定一下要解决的问题:识别链接中的中文参数是GBK编码还是UTF8编码(注意只有这两种编码情况,且不混合夹杂编码一半是GBK,一半是UTF8的情况)。

我们基于识别的理论基础是:

  • UTF-8 是兼容 ASCII 的,所以 0~127 就和 ASCII 完全一致了。
    GBK 的第一字节是高位为 1 的,第 2 字节可能高位为 0 。这种情况一定是 GBK ,因为 UTF-8 对 >127 的编码一定每个字节高位为 1 。
    另外,对于中文,UTF-8 一定编码成 3 字节。(似乎亚洲文字都是,UTF-8 中双字节好象只用于西方字符集)
    所以型如 110* 10** 的,我们一概看成 GBK/GB2312 编码。这就解决了“位”的问题。
    汉字以及汉字标点(包括日文汉字等),在 UTF8 中一定被编码成:1110 10** 10**

  • 连续汉字数量不是 3 的倍数的 GB2312 编码的汉字字符串一定不会被误认为 UTF-8 。用了一些GBK 扩展字,或是插入了一些 ASCII 符号的字符串也几乎不会被认为是 UTF-8 。

  • 一般说来,只要汉字稍微多几个,GBK 串被误认为 UTF-8 的可能性极其低。(只需要默认不使用 UTF-8 中双字节表示的字符)可能性低,这里还有另外一个原因。UTF-8 中汉字编码的第一个字节是 1110** ,这处于汉字的 GB2312 中二级汉字(不常用汉字,区码从 11011000 开始)的编码空间。一般是一些生僻字才会碰上。

所以,我们重点观察汉字被编码之后,是否符合1110 10** 10**这种情况。我们一开始假设是UTF-8编码,一旦发现不符合的话我们就反推认为是GBK编码。

实现上,正好和leetcode上一道题目相同 393. UTF-8 Validation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Solution {
public:
bool validUtf8(vector<int>& data) {
size_t len = data.size();
for( auto i = 0; i<len; i++ ){
if((data[i]|0x7f) == 0x7f) continue;
int cnt = 0;
for(int j=0;j<3;j++){
if((data[i]&mask[j]) == val[j]){
cnt = j+2;
break;
}
}
if(!cnt) return false;
if(i+cnt > len) return false;
for(int j=1;j<cnt;j++){
if((data[i+j]&0xc0) != 0x80) return false;
}
i += (cnt - 1);
}
return true;
}
private:
int mask[3] = {0xe0,0xf0,0xf8}; // 2,3,4
int val[3] = {0xc0,0xe0,0xf0};
};

额外要注意是对于一些标点符号保留字非ASCII码的单字节是跳过识别的。

线上已经跑了两年多了,再也没有乱码问题的反馈了。

引用

  1. 云风Blog 区分一个包含汉字的字符串是 UTF-8还是GBK