GBK和UTF-8的粗暴判断

原创 2012年05月26日 13:21:17

         最近遇到一些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正确判别出文件的字符集(尤其是UTF-8(无BOM)和GBK的判断)

最近在项目开发过程中遇到一个编码上的问题,原流程是用户上传csv数据并对数据入库。我们不知道用户会使用哪种编码的格式进行文件上传,而直接规定用户必须使用固定编码进行上传又感觉不够友好。所以需要我们程序...
  • dongping8887
  • dongping8887
  • 2018年01月13日 19:22
  • 273

判断一个字符串编码是utf-8还是gbk

java代码:     private static boolean Isutf8orgb2312(int[] value){         int iGBK = CountGBK(value); ...
  • burt_yu1
  • burt_yu1
  • 2017年02月09日 17:07
  • 1045

php中判断是gbk还是utf-8

// 返回: true - 含GB编码 false - 为UTF-8编码 function is_gb2312($str) {         for($i=0; $i          ...
  • bravezhe
  • bravezhe
  • 2013年07月20日 00:28
  • 1624

Android GBK与UTF-8

欢迎使用Markdown编辑器写博客本Markdown编辑器使用StackEdit修改而来,用它写博客,将会带来全新的体验哦: Markdown和扩展Markdown简洁的语法 代码块高亮 图片链接和...
  • hhbgk
  • hhbgk
  • 2015年06月26日 19:33
  • 2382

GBK和UTF-8转换的实现(C/C++)

一、利用iconv函数族进行编码转换 在Linux上进行编码转换时,既可以利用iconv函数族编程实现,也可以利用iconv命令来实现,只不过后者是针对文件的,即将指定文件从一种编码转换为另一种编码。...
  • u014563989
  • u014563989
  • 2016年11月11日 15:40
  • 363

彻底搞懂编码 GBK 和 UTF8

首先来看一下常用的编码有哪些,截图自Notepad++。其中ANSI在中国大陆即为GBK(以前是GB2312),最常用的是 GBK 和 UTF8无BOM 编码格式。后面三个都是有BOM头的文本格式,U...
  • VGWCIrO8NU
  • VGWCIrO8NU
  • 2017年03月06日 17:13
  • 1899

【编码】ASCII、Unicode、GBK和UTF-8字符编码的区别联系

ASCII、Unicode、GBK和UTF-8字符编码的区别联系2015-05-08 实验楼很久很久以前,有一群人,他们决定用8个可以开合的晶体管来组合成不同的状态,以表示世界上的万物。他们看到8个开...
  • u010262331
  • u010262331
  • 2015年05月26日 21:40
  • 5941

GBK和UTF-8的区别与相互转换

GBK和UTF-8的区别与相互转换简介GBK编码:是指中国的中文字符,其实它包含了简体中文与繁体中文字符,另外还有一种字符“gb2312”,这种字符仅能存储简体中文字符。 UTF-8编码:它是一种全...
  • junbincc02
  • junbincc02
  • 2016年11月09日 11:13
  • 649

如何选择下载版本,简体中文 GBK 与UTF-8 的区别

简体中文 GBK:   GBK是在国家标准GB2312基础上扩容后兼容GB2312的标准(好像还不是国家标准),文字编码是双字节来表示的,即不论中、英文字符均使用双字节来表示,只不过为区分中文,将其...
  • OceanRay1230
  • OceanRay1230
  • 2013年04月25日 11:26
  • 1262

Oracle-GBK和UTF8的区别,该选择哪个版本

gbk与utf8的区别和比较 GBK的文字编码是双字节来表示的,即不论中、英文字符均使用双字节来表示,只不过为区分中文,将其最高位都定成1。 至于UTF-8编码则是用以解决国际上字符的一种...
  • xiaohan2826
  • xiaohan2826
  • 2014年06月06日 16:07
  • 4625
收藏助手
不良信息举报
您举报文章:GBK和UTF-8的粗暴判断
举报原因:
原因补充:

(最多只允许输入30个字)