统计电脑中的文本文件由哪些编码构成?

电脑上最常见的文本编码格式有UTF-8和GBK,但是我最近遇到一个问题,我尝试用这两种编码对文本文件进行解析,却发现有的文件不能读取。

经过探测之后,发现有部分文件是UTF-16格式的。

于是我就想知道,电脑中的所有纯文本文件中,各种编码文件的占比是什么样的?

我可以用 chardet 库的 detect 函数可以对文件编码进行探测,但是得到的结果不够精确。

所以我设计了一个 Encoding 对象,用于统计指定文件是否符合某种编码:

  1. 对象属性中记录需要检测的编码格式、BOM匹配规则、和保存日志路径。

  2. 对象内的 check 方法传入待检测文件路径 path,尝试探测文件是否符合设定编码。

  3. 尝试以二进制模式打开路径,可能会遇到若干异常情况(文件不存在、文件权限不足、文件为网络路径无法打开等),如遇打开失败则认为检测失败。

  4. 如果没有捕获异常,则读取文件的前16个字节(为了提高速度),并判断是否和设定BOM保持一致。当BOM设定为空字节时,应总可以通过。

  5. 尝试以指定编码模式解码读取文本,如果解码错误则返回失败。如果编码类型为UNKNOWN,则跳过这一步骤。

  6. 如果成功读取文件内容,说明文件符合目标编码,将文件路径记录入日志,并更新计数值,返回结果为成功。否则返回结果为失败。

  7. 由于日志文件可能会被频繁读写,频繁打开文件可能导致写入失败,所以在创建Encoding对象时保存文件句柄,一次打开多次写入,最后统一关闭句柄资源。

Encoding类代码设计如下:

class Encoding:
    def __init__(self, encoding, suffix='', bom=b''):
        self.encoding = encoding
        self.bom = bom
        self.name = encoding + suffix
        self.log = open(f'{self.name}.txt', 'w', encoding='u8')
        self.count = 0

    def __repr__(self):
        return f"'{self.name}': {self.count}"

    def check(self, path):
        try:
            with open(path, 'rb') as f:
                assert f.read(16).startswith(self.bom)
            if self.encoding != 'unknown':
                with open(path, encoding=self.encoding) as f:
                    f.read()
            self.count += 1
            self.log.write(path + '\n')
            return True
        except Exception:
            return False

Python中遍历路径速度较慢,可用文件检索软件 Everything 更快速地获取电脑中的文件列表。

输入搜索语法:

ext:c;cpp;csv;cxx;h;hpp;htm;html;hxx;ini;java;lua;mht;mhtml;txt;xml;log size:<1MB

将文件列表保存为 filelist.txt,需要注意保存为TXT格式。

在完整代码中,按顺序设定9种编码检测规则,如果前面的检测已经成功则不再进行后面的检测。

将检测要求高的(比如对BOM有要求的)编码放在前面,编码规律不明显的GBK和BIG5放在Unicode编码的后面。

UNKNOWN放在最后面,这样前面8种规则都未能匹配的,就会落入UNKNOWN的统计范围。

但是如果在UNKNOWN编码的探测过程中,文件在一开始的打开时就已经失败,则不会落入任何一个统计项——因为未知原因读取不了文件内容,我不记录也罢。

然后程序打开 filelist.txt 文件,逐行读取文件路径,并用 Encoding 类中的 check 方法对文件编码进行探测。

完整代码如下:

import codecs

class Encoding:
    def __init__(self, encoding, suffix='', bom=b''):
        self.encoding = encoding
        self.bom = bom
        self.name = encoding + suffix
        self.log = open(f'{self.name}.txt', 'w', encoding='u8')
        self.count = 0

    def __repr__(self):
        return f"'{self.name}': {self.count}"

    def check(self, path):
        try:
            with open(path, 'rb') as f:
                assert f.read(16).startswith(self.bom)
            if self.encoding != 'unknown':
                with open(path, encoding=self.encoding) as f:
                    f.read()
            self.count += 1
            self.log.write(path + '\n')
            return True
        except Exception:
            return False

    def close(self):
        self.log.close()

encodings = [
    Encoding('ascii'),
    Encoding('utf-8', '-bom', codecs.BOM_UTF8),
    Encoding('utf-8'),
    Encoding('gbk'),
    Encoding('big5'),
    Encoding('utf-16', '-bom-le', codecs.BOM_UTF16_LE),
    Encoding('utf-16', '-bom-be', codecs.BOM_UTF16_BE),
    Encoding('utf-16'),
    Encoding('unknown'),
]

with open('filelist.txt', encoding='u8') as f:
    paths = f.read().splitlines()

print('total:', len(paths))

for cnt, path in enumerate(paths):
    if cnt % 1000 == 0:
        print(cnt, encodings)
    any(en.check(path) for en in encodings)

for en in encodings:
    en.close()

print(cnt + 1, encodings)

我分别在两台电脑上运行测试,得到统计结果如下:

编码计数占比
ascii11722960.02%
utf-8-bom39132.00%
utf-84727024.20%
gbk2199311.26%
big51510.08%
utf-16-bom-le16970.87%
utf-16-bom-be370.02%
unknown30151.54%
总数195305100.00%

另一台电脑:

编码计数占比
ascii59458988.58%
utf-8-bom50210.75%
utf-8385795.75%
gbk162182.42%
big51600.02%
utf-16-bom-le19090.28%
utf-16-bom-be00.00%
unknown147892.20%
总数671265100.00%

两台电脑上测试结果基本一致,基本结论如下:

  1. 除去只包含纯英文字符的ASCII文件,占比最高的是UTF-8编码。但是其中约有10%含有3字节的BOM文件头。

  2. UTF-8编码和GBK(本地ANSI)的编码占比约为2:1,并且为电脑中占比最高的2种编码,这是和预期一致的。

  3. 意料之外的干扰项是UTF-16编码,其中以Big-Endian居多,和当前电脑的字节序一致。这一部分占GBK编码的约1/10。

  4. 然后还有极少量的UTF-16-BE编码和繁体字的BIG5编码。

  5. 一些无法探测的编码文件手动确认后发现,有部分存在俄文和日文,还有的存在局部乱码。由于文本并未全部符合某一种编码类型,所以也会认为检测失败。

另外说到UTF-16编码,我曾考虑过是否存在某些文件不包含BOM头,但是也符合UTF-16编码。但是实际情况是,在不含有BOM的1200多个能被UTF-16解码的文件中,经手动确认,100%全部都是误检测(但是含有一个UTF-32文件)。

这是由于无BOM的UTF-16编码格式只有非常弱的编码规律。生成一段随机的ASCII文本,都有可能可以按照UTF-16编码进行解码,当然解码出来的文字都是乱码。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值