clq
浏览(275) +
2024-01-31 22:46:34 发表
编辑
关键字:
[2024-03-05 18:48:27 最后更新]
判断字节数组字节流是否是 utf8 的算法
以前用的一些取巧算法,下面这个更严谨。否则我发现用来判断 “中文” 的 gbk 都会错认为是 utf8 。
其实目前的 utf8 编码有两个比较好理解的特征,即除单字节的 utf8 字符外,它的字符中的非第一个字节都是二进制 10 开头的,即 10xxxxxx
也就是说除首字节外都是 0x80 (含) 到 0xC0 (不含) 之间的字符。
其中
c0 = 11000000
80 = 10000000
而首个字节更有意思,除单字节的字符外它必定是以二进制 110, 1110, 11110 ... 一直到 1111110
这首字节中那 0 前面的 1 有多少个就表示它是由几个字节组成的字符 ... 牛吧。
目前来说最多是 6 个字节,但是按这个算法,显然可以是 7 个字节。
原理来自 https://zh.wikipedia.org/zh-cn/UTF-8
下面是一个改进的判断算法,实际严格判断了首个字节外的其他字节。这其实也不太对,首字节的范围还是应该严格计算。不过目前这个确实也比较准确了。
我主要用的比较,而不是常见的字节的 “或” 语法,主要是考虑到某些语言中对这种操作的支持不是太简洁。
关于首字节的算法改进,以后有时间再说吧。其实我已经说得很清楚了,大家可以自己改。
--------------------------------------------------------
//v2 - 第二版本。原来的第一个版本还有问题。注意有些旧代码还是第一版本
//网络上的已经是正确的。在 http://newbt.net/ms/vdisk/show_bbs.php?id=5D0E1A42600A41F8D86B98491DE43F55&pid=164
//这是优先在 windows 版本下修复成功的。因此 mac 下的其他版本可能是有问题的。
//2024.03.05 更新
//从原理上判断是否是 utf8
//对于 govcl mac 版来说,太重要,所以用了 golang 的语法 //指的返回值语法
func is_utf8(buf [] byte) (r bool) {
defer PrintError("is_utf8");
r = false;
count := len(buf);
//--------------------------------------------------------
//参考 delphi 程序 F:\clq\utf8_decode
//官方说明参考 http://zh.wikipedia.org/zh-cn/UTF-8
//--------------------------------------------------------
//single:AnsiString;
//b,b1,b2,b3,b4:Byte;
//index_in_single:Integer; //当前计算到了 single 单个 utf8 字符中的第几个字节了
//single:AnsiString;
var b,b1,b2,b3,b4 byte;
var index_in_single int; //当前计算到了 single 单个 utf8 字符中的第几个字节了
index_in_single = 0;
var is_utf8 bool;
//is_utf8 = false;
is_utf8 = true;
for i := 0; i < count; i++ {
b = buf[i];
//其实 utf8 是根据第一个字节来推断一个字符占用几个字节的
//小于 0x80 bin(1000 0000) 为单字节
//小于 0xe0 bin(1110 0000) 为双字节
//小于 0xf0 bin(1111 0000) 为三字节
//小于 0xf8 bin(1111 1000) 为4字节
//但它们对第二位及后面的字节也并不是全部占完的,似乎是为了方便在任意位置分割字符串
//--------------------------------------------------------
//2024 更新,根据 https://zh.wikipedia.org/zh-cn/UTF-8
// Byte 1 Byte 2 Byte 3 Byte 4 Byte 5 Byte 6
// 7 U+0000 U+007F 1 0xxxxxxx
// 11 U+0080 U+07FF 2 110xxxxx 10xxxxxx
// 16 U+0800 U+FFFF 3 1110xxxx 10xxxxxx 10xxxxxx
// 21 U+10000 U+1FFFFF 4 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
// 26 U+200000 U+3FFFFFF 5 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
// 31 U+4000000 U+7FFFFFFF 6 1111110x 10xxxxxx
//----
//可知,除第一个字节外,后面字节的取值范围都是 10xxxxxx ,即是大于 0x80 的
//“注意在多字节串中,第一个字节的开头"1"的数目就是整个串中字节的数目” 这个信息也很是重要,不过我没用这个判断
//实际上还是要用,否则 “中文” 的 gbk 就会判断为 utf8
//--------------------------------------------------------
if (0 == index_in_single){
b1 = b;
if b1 < 0x80 { //if b1 <= $007f then //0x007f //单字节字符 //0zzzzzzz(00-7F)//7f的二进制是 01111111 第一个字节第一位为 0 的都是单字节字符
is_utf8 = true;
//single := AnsiChar(b1);
//Result := Result + single + #13#10;
index_in_single = 0; //重置位置
continue;
}//if b1
}//if index_in_single 0
//--------------------------------------------------------
if (1 == index_in_single){
b2 = b;
if (b1 < 0xe0) {
//它的意思是说第一个字节全部用完都为1的情况下用第2个字节的一半表示双字节的字符
//--------------------------------------------------------
//和 delphi 版本不同,实际上第二个字节也要在范围内才是合法的 utf8
if (b < 0x80){
is_utf8 = false; //当前字节超过范围了,那就不是 utf8 //除第一个字节外,都是 10xxxxxx ,即是大于 0x80 的
break;
}//
if (b >= 0xC0){ //c0 = 11000000
is_utf8 = false; //当前字节超过范围了,那就不是 utf8 //除第一个字节外,都是 10xxxxxx ,即是大于 0x80 的
break;
}//
//--------------------------------------------------------
//single := AnsiChar(b1) + AnsiChar(b2);
//single := Utf8ToAnsi(single) + '| - 占用 2 字节'; //这个是为了还原为 gbk 而已
//Result := Result + single + #13#10;
index_in_single = 0; //重置位置
continue;
}//if b1
}//if index_in_single 1
//--------------------------------------------------------
if (2 == index_in_single){
b3 = b;
if (b1 < 0xf0) {
//--------------------------------------------------------
//和 delphi 版本不同,实际上第二个字节也要在范围内才是合法的 utf8
if (b < 0x80){ //实际上这个算法还不够准确,要判断第二位是 0 才更对
is_utf8 = false; //当前字节超过范围了,那就不是 utf8 //除第一个字节外,都是 10xxxxxx ,即是大于 0x80 的
break;
}//
if (b >= 0xC0){ //c0 = 11000000
is_utf8 = false; //当前字节超过范围了,那就不是 utf8 //除第一个字节外,都是 10xxxxxx ,即是大于 0x80 的
break;
}//
//--------------------------------------------------------
//single := AnsiChar(b1) + AnsiChar(b2) + AnsiChar(b3);
//single := Utf8ToAnsi(single) + '| - 占用 3 字节'; //这个是为了还原为 gbk 而已
//Result := Result + single + #13#10;
index_in_single = 0; //重置位置
continue;
}//if b1
} //if index_in_single 2
//--------------------------------------------------------
if (3 == index_in_single){
b4 = b;
if (b1 < 0xf8) { //目前来说其实不用判断也可以,除非以后扩展到超过 4 个字节了
//--------------------------------------------------------
//和 delphi 版本不同,实际上第二个字节也要在范围内才是合法的 utf8
if (b < 0x80){
is_utf8 = false; //当前字节超过范围了,那就不是 utf8 //除第一个字节外,都是 10xxxxxx ,即是大于 0x80 的
break;
}//
if (b >= 0xC0){ //c0 = 11000000
is_utf8 = false; //当前字节超过范围了,那就不是 utf8 //除第一个字节外,都是 10xxxxxx ,即是大于 0x80 的
break;
}//
//--------------------------------------------------------
//single := AnsiChar(b1) + AnsiChar(b2) + AnsiChar(b3) + AnsiChar(b4);
//single := Utf8ToAnsi(single) + '| - 占用 4 字节'; //这个是为了还原为 gbk 而已
//Result := Result + single + #13#10;
index_in_single = 0; //重置位置
continue;
}//if b1
}//if index_in_single 3
//--------------------------------------------------------
index_in_single = index_in_single + 1;
if (index_in_single > 3){
is_utf8 = false; //这里认为最多用 4 个字节,其实也不一定对,实际上可以用 6 个,只要保留最后一个 10 位就可以了
//更严格的算法是首字节只能是(二进制) 10, 110, 1110, 11110 ... 这样的,即 n 个 1 之后一定要有一个 0
break;
}//
//--------------------------------------------------------
}//
if (false) { fmt.Println( b,b1,b2,b3,b4); }
r = is_utf8;
return r;
}//
NEWBT官方QQ群1: 276678893
可求档连环画,漫画;询问文本处理大师等软件使用技巧;求档softhub软件下载及使用技巧.
但不可"开车",严禁国家敏感话题,不可求档涉及版权的文档软件.
验证问题说明申请入群原因即可.