可乐爱上咖啡

致力于高性能服务器和小众语言的研究-个人笔记杂货摊

GBK和UTF-8的粗暴判断

         最近遇到一些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字节    1111110 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;
}

阅读更多
个人分类: 其他
想对作者说点什么? 我来说一句

Java判断文件的编码

2012年04月09日 3KB 下载

没有更多推荐了,返回首页

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭