判断一个文件是不是UTF-8编码的文本文件的Java实现

前言

为什么写这篇博客?

闲来没事看到了老代码,发现有一段的文件是否未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));
}

时序图解析

Created with Raphaël 2.1.2 输入流 UTF-8标记位? 判定为是100% 百分百一般定为90%(可变) 通过逐个字节的分析 不可能的字节? 判定为否0% 分析字节的组成 得到UTF-8格式字节占比 yes no yes no

实现原理的解析

也就是对源码的分析:

  • 对 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

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值