最近遇到一些url中携带没有encode掉的汉字,并且这样的url有的是utf-8编码,有的又是gbk编码。最终这些url被记录下来的时候,必然就有一类url是乱码了。有人说,汉语博大精深,可恰恰也是这些博大精深的东西时不时的让我们伤透脑筋。我认为这种url编码的情况,最专业的手法应该是url上有一个参数用来说明编码。现在既然没有人告诉我这个url是什么编码,那么我也得尽力判断,然后转换编码。
识别url的编码有个难点是其中的非ascii字符数量太少,容易误判。比如:我遇到的url中的非ascii字符一般就是搜索关键字。不过,简单的一面是url中的非ascii字符一般都是汉字,基本不会有特殊字符,西方字符等。因此,就将目标锁定在汉字的检测上,这基本足以满足需求,至少可以将这类乱码情况降低不少。
utf-8为一种变长编码字符集,也就是说一个字符可能由1个字节、2个字节、3个字节、4个字节,最多6个字节来表示。每种字节长度的编码模式如下:
字节数 | 二进制 | 说明 |
1字节 | 0xxxxxxx | ASCII字符 |
2字节 | 110xxxxx 10xxxxxx | |
3字节 | 1110xxxx 10xxxxxx 10xxxxxx | 汉字 |
4字节 | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx | |
5字节 | 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx | |
6字节 | 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx |
有了上表中的变长编码模式,再结合云风总结的博客 (http://blog.codingnow.com/2010/06/detect_utf-8_gbk.html),基本可以得出如下的一个结论:
url中的非ascii字符只是汉字,那么遇到非ascii字符,就按utf-8的3字节编码规则去识别,匹配上,就认为是一个utf-8的字符,当url中所有的非ascii都能够和utf-8的3字节编码模式匹配时,就可以认为此编码是utf-8。那么,就可以将这个url转换到gbk了。这种判定方法显然是简单粗暴的,但应该可以解决绝大多数的情况。
云风博文中提到只要连续汉字数量不是3的倍数,就一定不会将gbk误认为UTF-8。这是因为gbk采用双字节对汉字进行编码,为了和单字节的ascii编码区分开,因此最高位一定是1, 除此没有规律可循了。也就是说GBK的汉字编码模式是:1xxxxxxx xxxxxxxx。现在假设我们的搜索关键字是3个汉字,这个3个汉字的gbk编码后的二进制大概长如下样子:
汉字1 | 汉字2 | 汉字3 |
1110xxxx 10xxxxxx | 10xxxxxx 1110xxxx | 10xxxxxx 10xxxxxx |
根据上表可以清楚的看到,3个汉字gbk编码后,按UTF-8 3字节编码模式可能被误判为两个汉字,红色和蓝色分别组成了新的汉字。不过,仔细想想3个汉字同时要符合这样的一个模式,概率应该是挺低的。从学术的角度来说,这个方法确实太简单粗暴了,但从工业生产的角度来看,只要能够很好的解决实际问题的方法就是好方法。最近刚好有测试系统在跑,把这个加入进去看看,实际效果究竟如何,能不能处理掉99%的情况。
还有一种方法应该更加的准确——假设url是gbk,那么首先将其转换到utf-8,然后在转换回GBK,最后逐字和原始url对比字节,看是否相同。这种方法就是要倒腾两次,作为需要处理海量url的服务器,性能上需要考虑。
附上代码(欢迎指正):
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#define YES 0
#define NO -1
#define ASCII__MASK 0x80
#define CHINESE_MASK1 0xF0
#define CHINESE_MASK2 0xC0
#define CHINESE_MASK3 0xC0
#define ASCII_VALUE 0x00
#define CHINESE_VALUE1 0xE0
#define CHINESE_VALUE2 0x80
#define CHINESE_VALUE3 0x80
int check_utf8_chinese(u_char *data, size_t len)
{
u_char ch;
size_t i, count, fail;
enum {
chinese1 = 0,
chinese2,
chinese3
} state;
state = chinese1;
count = 0;
fail = 0;
for (i = 0; i < len; i++) {
ch = *(data + i);
if (ch <= 127) {
continue;
}
switch (state) {
case chinese1:
if (!((ch & CHINESE_MASK1) ^ CHINESE_VALUE1)) {
state = chinese2;
} else {
fail++;
}
break;
case chinese2:
if (!((ch & CHINESE_MASK2) ^ CHINESE_VALUE2)) {
state = chinese3;
} else {
state = chinese1;
fail++;
}
break;
case chinese3:
if (!((ch & CHINESE_MASK3) ^ CHINESE_VALUE3)) {
count++;
} else {
fail++;
}
state = chinese1;
}
}
return fail != 0 ? NO : YES;
}
int main(int argc, char **argv)
{
int fd, n, ret;
u_char buffer[1024];
fd = open(argv[1], O_RDONLY);
if (fd < 0) {
perror("open");
}
n = read(fd, buffer, 1024);
if (n < 0) {
perror("read");
}
ret = check_utf8_chinese(buffer, n);
printf("%s\n", (ret == NO ? "gbk" : "utf-8"));
return 0;
}