GBK和UTF-8的粗暴判断

来源:互联网 发布:茶叶淘宝网 编辑:程序博客网 时间:2024/05/08 05:38

         最近遇到一些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字节   111111010xxxxxx  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汉字31110xxxx  10xxxxxx10xxxxxx  1110xxxx10xxxxxx  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  0x80int 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;}