区分一个包含汉字的字符串是 UTF-8 还是 GBK
今天检查 svn 仓库,发现又有同学没按规定提交包含汉字的代码。我们规律,所有源文件中包含的汉字必须使用 UTF-8 编码方式,而不能使用 GBK 。
总这么人工检查也不是个事。所以我想写一个 svn 的钩子,在提交前检查。在仓库的 hooks/pre-commit.teml 加一行检查脚本应该就可以了。
我想用正则表达式匹配一下,可是想了想又觉得 UTF-8 和 GBK 的编码集有点交集,不太好做。btw, google 了一下,的确有人写过特定编码的正则表达式。
继续 google ,找到一篇跟我需求有点类似的文章UTF-8编码检测失败特例。看了正文,觉得不太靠谱,然后继续看回复,觉得这方法可行。
然后定睛一看,原来文章是孟岩写的,回复是我自己三年多前回复在他的 blog 上的。 -_-
打算还是自己写个小程序做检查,不用现成工具了。
具体算法复制回这里:
cloudwu 发表于2007-01-05 00:49:51 IP: 218.72.15.*
如果想区分一个完整的字符串是 GBK 还是 UTF8 其实蛮简单的。 虽然做不到 100% 有效,但也比上面的方法强许多。
UTF8 是兼容 ascii 的,所以 0~127 就和 ascii 完全一致了。
gbk 的第一字节是高位为 1 的,第 2 字节可能高位为 0 。这种情况一定是 gbk ,因为 UTF8 对 >127 的编码一定每个字节高位为 1 。
另外,对于中文,UTF8 一定编码成 3 字节。(似乎亚洲文字都是,UTF8 中双字节好象只用于西方字符集)
所以型如 110***** 10******
的,我们一概看成 gbk/gb2312 编码。这就解决了“位”的问题。
汉字以及汉字标点(包括日文汉字等),在 UTF8 中一定被编码成:1110**** 10****** 10******
连续汉字数量不是 3 的倍数的 gb2312 编码的汉字字符串一定不会被误认为 UTF8 。用了一些gbk 扩展字,或是插入了一些 ascii 符号的字符串也几乎不会被认为是 UTF8 。
一般说来,只要汉字稍微多几个,gbk 串被误认为 UTF8 的可能性极其低。(只需要默认不使用 UTF8 中双字节表示的字符)可能性低,这里还有另外一个原因。UTF8 中汉字编码的第一个字节是 1110****
,这处于汉字的 gb2312 中二级汉字(不常用汉字,区码从 11011000 开始)的编码空间。一般是一些生僻字才会碰上。
Comments
很赞同这个方法。
Posted by: 福利工口姬 | (16) April 16, 2014 03:58 PM
@dwing gcc 4.4能识别BOM了。
Posted by: Changming Sun | (15) February 27, 2011 11:10 AM
如果偏要追求辅助方法的话,分别用GBK和UTF-8的语言模型算一个分数,倒的确是很准的。。。
Posted by: myxxn | (14) February 25, 2011 02:35 PM
最好的办法还是分别解码,然后看哪一种解法得到的汉字字频更高
Posted by: LianQiao | (13) July 19, 2010 07:13 PM
Mozilla Firefox 源码里面有个自动检测字符编码的模块 universalchardet,可以自动识别(或者说猜测)代码的编码类型,估计每个浏览器都干过这个事情。用过它的python封装Character做过类似的工作。
Posted by: Space | (12) July 19, 2010 05:10 PM
mbstring 的 mb_detect_encoding检查编码准确性并不是非常的高。大概只有一般的成功率。有空验证一下你的方法的准确度有多少。
Posted by: niniwzw | (11) July 10, 2010 12:08 PM
以后都用utf算了,gbk有时不方便
Posted by: 阳江佬 | (10) July 6, 2010 10:44 AM
学习了
Posted by: 无名氏 | (9) July 6, 2010 10:35 AM
既然是脚本作的话,可以看看python的一个拓展库chardet,可以检测到字符的编码,然后就可以使用python来转化了,之前这么做过
Posted by: 关中刀客 | (8) June 30, 2010 01:16 PM
学习一下,支持沙发的。
Posted by: QQ农场 | (7) June 30, 2010 11:09 AM
可用如下代码确认是不是汉字
int isChinese(unsigned char bhead, unsigned char btail)
{
int r=0;
int iHead = bhead & 0xff;
int iTail = btail & 0xff;
if ((iHead>=0x81 && iHead<=0xfe &&
(iTail>=0x40 && iTail<=0x7e || iTail>=0x80 && iTail<=0xfe)) ||
(iHead>=0xa1 && iHead<=0xf7 && iTail>=0xa1 && iTail<=0xfe) ||
(iHead>=0xa1 && iHead<=0xf9 &&
(iTail>=0x40 && iTail<=0x7e || iTail>=0xa1 && iTail<=0xfe)))
{
return 1;
}
return 0;
}
Posted by: anysql | (6) June 30, 2010 07:46 AM
如果只是用在 svn 钩子里的话,可以试试 iconv --from-code=UTF-8//IGNORE --to-code=UTF-8 过滤一遍后是否和原来的文本完全一样
==========================
顶这个方法.
Posted by: abadcafe | (5) June 30, 2010 12:57 AM
C/C++代码中使用UTF-8编码有个很麻烦的问题:
VisualStudio的IDE认定代码是否UTF-8仅仅依靠BOM,而不是猜测;而GCC却无法忽略UTF-8的BOM.
结果我最后还是妥协用GBK编码,两个编译器都能正常识别.
Posted by: dwing | (4) June 29, 2010 07:25 PM
聪明人的脑袋不能这么浪费啊。
推荐 Davies 方法。
Posted by: Anonymous | (3) June 29, 2010 04:54 PM
如果只是用在 svn 钩子里的话,可以试试 iconv --from-code=UTF-8//IGNORE --to-code=UTF-8 过滤一遍后是否和原来的文本完全一样
如果不一样则尝试 from gbk to utf8,再 from utf8 to gbk,看看被转了两遍后是否跟原文本一样,如果是的话则确认是 gbk 字符,最终 from gbk to utf8
在不需要考虑效率的场合,这种方法应该是很土但是很可靠的
Posted by: Platinum | (2) June 29, 2010 04:25 PM
其实都不要考虑这么复杂,直接拿diff尝试做utf-8解码,如果出错就不允许提交就行了。
Posted by: Davies | (1) June 29, 2010 04:24 PM