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  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  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;
}

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值