前言
为什么写这篇博客?
闲来没事看到了老代码,发现有一段的文件是否未UTF-8编码的实现很有趣。
同时联想到这段代码的学习者可以通过这段代码去了解 字符集
和 编码方式
。 因此供给大家参考。
这篇博客的功能?
这篇博客针对于磁盘上的一个文件(可能是图片,也可能是视频、音乐、Excel、可执行程序等), 判断它是不是一个 UTF-8格式的文本文件
这篇博客全面么?
显然是不全面的, 也没有找到最新最优的论文来实现一个100%可判定准确的程序。
但是经过了长达一年的测试, 文章的实现方式在普通WEB生产服务虾是可行可用可被参考的。
实现的源码解析
实现很简单:输入一个文件的字节流,输出这个文件中的UTF-8格式编码的占比(当判定一个文件一定不是UTF-8格式编码的文本文件
的时候,直接返回0)。 一般情况下占比大于90%就可断定这是一个UTF-8格式的文本编码文件了。
源码及注释
/**
* 在一段字节码里面找到符合 UTF-8 编码的字节数量. 取其数量占比
* 同时考虑到了 BOM 位 (仅在初始值时) 未针对 Unicode 其它编码方式进行判断。
* 该端代码具有很强的针对性, 如果文件是文本则可以很高效的分辨出是不是UTF-8, 但是如果是二进制文件, 则存在较大几率上的误差.
* <p>
* 参照格式为:
* 0x0*******
* <p>
* 0x110*****
* 0x10******
* <p>
* 0x1110****
* 0x10******
* 0x10******
* <p>
* 0x11110***
* 0x10******
* 0x10******
* 0x10******
* <p>
* 0x111110**
* 0x10******
* 0x10******
* 0x10******
* 0x10******
* <p>
* 0x1111110*
* 0x10******
* 0x10******
* 0x10******
* 0x10******
* 0x10******
*
* @param raw 指定的字节码
*
* @return 数量占比
*/
private static int utf8(byte[] raw) {
int i, len;
int utf8 = 0, ascii = 0;
if (raw.length > 3) {
if ((raw[0] == (byte) 0xEF) && (raw[1] == (byte) 0xBB) && (raw[2] == (byte) 0xBF)) {
return 100;
}
}
len = raw.length;
int child = 0;
f:
for (i = 0; i < len; ) {
// UTF-8 中一定没有 FF 和 FE
if ((raw[i] & (byte) 0xFF) == (byte) 0xFF || (raw[i] & (byte) 0xFE) == (byte) 0xFE) {
return 0;
}
if (child == 0) {
if ((raw[i] & (byte) 0x7F) == raw[i] && raw[i] != 0) {
// ASCII 在所有编码中格式为 0x0*******
ascii++;
} else if ((raw[i] & (byte) 0xC0) == (byte) 0xC0) {
// 如果是 0x11****** 形式的, 则有一定可能性是 UTF-8 的
for (int bit = 0; bit < 8; bit++) {
if ((((byte) (0x80 >> bit)) & raw[i]) == ((byte) (0x80 >> bit))) {
child = bit;
} else {
break;
}
}
utf8++;
}
i++;
} else {
child = (raw.length - i > child) ? child : (raw.length - i);
boolean currentNotUtf8 = false;
for (int children = 0; children < child; children++) {
// 格式必须是 10******
if ((raw[i + children] & ((byte) 0x80)) != ((byte) 0x80)) {
if ((raw[i + children] & (byte) 0x7F) == raw[i + children] && raw[i] != 0) {
// ASCII 在所有编码中格式为 0x0*******
ascii++;
}
currentNotUtf8 = true;
}
}
if (currentNotUtf8) {
utf8--;
i++;
} else {
utf8 += child;
i += child;
}
child = 0;
}
}
// 纯ascii的, 也可以理解为纯 utf-8 的。
if (ascii == len) {
return 100;
}
// 把 ascii 的也算成 utf-8 的。
return (int) (100 * ((float) (utf8 + ascii) / (float) len));
}
时序图解析
实现原理的解析
也就是对源码的分析:
- 对 EF,BB,BF位做了判定. 这是微软对UTF-8格式文件的小窍门。有这个标记,则可以判定为
UTF-8编码的文本
- UTF-8编码方式中, 一定没有 FF 和 FE 这两种字节;其它编码方式也没有。有这种字节的文件, 一定不是文本文件。
- 一个字节如果是ASCII(0x0*** ****), 则这个字节认定为是UTF-8编码。
- 一个字节如果类似 0x1*** ****:
- 如果格式类似于 0x11** **** 则大概符合UTF-8的编码方式
- 接下来就需要判断 这个字节的8个0/1位中, 从左起有多少(
N
)个1。 - 在接下来的
N-1
个字节中,如果都满足 0x10** ****,则这连续的N
个字节可以猜想为UTF-8编码。
- 判定接下来的
N-1
个字节:
- 为了满足 0x10** **** 格式, 只要有任意一个字节不满足, 则这段字节都不会是UTF-8编码
- 但是接下来的这些字节,如果是ASCII编码还是得被统计计数(以防一段连续的编码中有乱码,导致漏统计)
- 对于每次对ASCII编码和UTF-8编码的发现, 都分别对计数器加1(ASCII)或者加N(UTF-8变长, 统计的是所有的字节中UTF-8的占比, 一个UTF-8字符需要被拆解成N个字节分别统计)
- 对于ASCII和UTF-8,不叠加统计,优先统计为 ASCII
所有的字节
全都是ASCII编码时,一定是
UTF-8编码的文本文件- ASCII + UTF-8 的字节数占比占到全部字节数的
90%
(比例自行拿捏)时,也可以理解为是UTF-8编码的文本文件
技术衡量
本文的技术考量中没有考虑到有BOM位的UTF-8格式文本(这种Case比较少,更何况有10%的缓冲空间)
本文仅针对UTF-8的编码方式进行分析,目前没有发现合适的论文、说明来论证这个本文的准确性(但是一篇文章中有90%的字节都能够符合UTF-8的编码方式,如果真不是UTF-8编码文本,这样的巧合也是很难得了)
一般情况下不需要对文件全文进行判定。随机抽取前1024(或者更多)个字节,判定率达到了90%也就可以理解为UTF-8格式的文本文件了。
本文便是基于一般二进制文件(图片、视频、音乐、EXE、EXCEL等)编码都是一些随机的二进制串,理论上不会满足UTF-8的编码方式而研发的。
本文的实践准确度很高。
参考这篇博客:http://www.ruanyifeng.com/blog/2007/10/ascii_unicode_and_utf-8.html