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


    电脑上最常见的文本编码格式有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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    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)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55

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

    编码计数占比
    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编码进行解码,当然解码出来的文字都是乱码。

  • 相关阅读:
    【机器学习】评价指标 : 准确率,查准率与查全率
    线代 | 线性代数的本质 本质 本质 nature
    大模型:如何利用旧的tokenizer训练出一个新的来?
    【无标题】
    阿里巴巴云生态 9 大开源项目重磅发布
    TortoiseGit安装和配置详细说明
    nacos安装详细教程-单机、集群(nginx)
    leetcode 136. 只出现一次的数字
    爱情中不需要太多“礼貌”
    基于FPGA的视频接口之千兆网口(四配置)
  • 原文地址:https://blog.csdn.net/weixin_39804265/article/details/133753284