创建一个自己的归档文件

关于压缩包大家并不陌生,rar、tar、gz、zip、7z等都是常用压缩包后缀文件名称,以zip包为例举例说明。

vim 1.txt
写入test
zip test.zip 1.txt

为什么zip是压缩文件,怎么还比原文件大?原因是它的机制导致,zip包不仅要存储1.txt里面信息还要存储文件名称路径和时间等信息以及zip包自带的一些后缀信息位等导致的。

根据zipfile库(python3.7内置库)可以推算出这个计算关系,五一前在pycharm里面debug一点点研究出来过,不过忘记记录了,但不影响我们学习原理,下面用SublimeText看一下zip包内容:

里面用的是2进制存储的,打开展示出来是16进制的,里面有330个字符,每个字符是16进制,16=2**4,所以一个16进制数占用4bit,所以文件大小为165B。

为什么要创建一个自己的压缩包?目前现有的压缩包无法把自己内部相同的文件名去除,不管你是使用linux命令还是使用zipfile库的方法都不能够做到,这就是我的需求。

可以自己debug一下,zipfile源码里有一个ZipInfo类,它存储所有压缩进来的文件信息,通过namelist、filelist等属性得到文件信息。filelist里面每个文件都采用一个dict存储文件信息,所以即使同名也会存储在一起,因为他们就算同名也因为插入压缩包的有先后循序,然后被写在zip文件16进制不同位置。在解压的时候先插入的会被后插入的文件在解压后因为同名被操作系统自动覆盖,这就导致我们以为压缩包只存储一个同名文件(这里还有个致命缺点)。

虽然zipfile对象里面还有一个属性NameInfo的list存储了所有进入压缩包最新的文件名,可以通过这个list,使用seek方法移动文件流指针找到待解压相关文件的位置进行解压,这个也能解决我的需求。但是刚才上面提到一个致命缺点,缺点就是你在这个压缩包插入很多同名大文件时,它像貔貅一样毫不犹豫的全部吃进去,虽然解压出来是最新的,但是自身会特别大。

zipfile._ZipWriteFile.close():

举例:

压缩包里面存储了1024个文件,每个文件1MB左右,该压缩包大小约为1GB。其中因为一些原因,里面有些文件会更新修改比较频繁,客户对里面102个文件各进行了修改10次,操作完之后压缩包变成了约2GB。哈哈,成功干掉公司服务器。

按理说这个压缩包怎么修改里面的一个文件,大小应该在1GB左右。所以我要针对zip做一些优化,来帮我去重,代码如下。

# coding:utf-8
# author:mjTree@foxmail.com
import re
import zipfile




class InMemoryZipFile(object):


    def __init__(self, file_name=None, compression=zipfile.ZIP_DEFLATED, debug=0):
        try:
            from cStringIO import StringIO
        except ImportError:
            from io import BytesIO as StringIO
        if hasattr(file_name, '_from_parts'):
            self._file_name = str(file_name)
        else:
            self._file_name = file_name
        self.in_memory_data = StringIO()
        self.in_memory_zip = zipfile.ZipFile(
            self.in_memory_data, "w", compression, False)
        self.in_memory_zip.debug = debug


    @property
    def data(self):
        return self.in_memory_data.getvalue()


    def append(self, filename_in_zip, file_contents):
        self.in_memory_zip.writestr(filename_in_zip, file_contents)
        return self


    def write_to_file(self, filename):
        for zip_file in self.in_memory_zip.filelist:
            zip_file.create_system = 0
        self.in_memory_zip.close()
        with open(filename, 'wb') as f:
            f.write(self.data)


    def delete_from_zip_file(self, pattern=None, file_names=None):
        if pattern and isinstance(pattern, str):
            pattern = re.compile(pattern)
        if file_names:
            if not isinstance(file_names, list):
                file_names = [str(file_names)]
            else:
                file_names = [str(f) for f in file_names]
        else:
            file_names = []
        with zipfile.ZipFile(self._file_name) as zf:
            for zf_info in zf.infolist():
                if zf_info.filename in file_names:
                    file_names.remove(zf_info.filename)
                    continue
                if pattern and pattern.match(zf_info.filename):
                    continue
                self.append(zf_info.filename, zf.read(zf_info))


    def __enter__(self):
        return self


    def __exit__(self, exc_type, exc_value, traceback):
        if self._file_name is None:
            return
        self.write_to_file(self._file_name)




def delete_from_zip_file(file_name, pattern=None, file_names=None):
    with InMemoryZipFile(file_name) as imz:
        imz.delete_from_zip_file(pattern, file_names)


使用方法:

def insert_dict_to_zip(zip_file_path, file_path, data):
    if os.path.exists(zip_file_path):
        with zipfile.ZipFile(zip_file_path, 'r') as zip_file:
            if file_path in zip_file.namelist():
                delete_from_zip_file(zip_file_path, file_names=file_path)
    with zipfile.ZipFile(zip_file_path, 'a', compression=zipfile.ZIP_DEFLATED) as zip_file:
        zip_file.writestr(file_path, json.dumps(data, ensure_ascii=False))

貌似解决了,但是有对比就有伤害,速度没有达到我的预期要求。对标速度怎么来的?还是上面1024个文件每个1MB的例子举例,如果我把1024个文件存在同一个文件夹下面,修改其中一个文件时,直接生成同名文件然后让操作系统帮我把旧文件覆盖。在业务场景下,系统替换文件速度是前面优化zip方案的4倍以上(偶尔存在文件很大时倍数更大)。

了解了zip包的存储原理,那就借鉴zipfile的方法自己去创建一个简单的压缩包。压缩包后缀随意更换,实际就是一个txt文件,文件的每一行存储原来一个文件的内容。存储前使用zlib/gzip库先进行一次压缩操作得到二进制,然后把二进制转成base64码的str,因为直接把二进制bytes变成str存储,再读取恢复时存在一些问题。

这个新方案的速度很接近系统文件替换的速度,业务场景下测试大文件时,系统替换速度也不会超过2倍。而且还能够解决服务器存在很多很多小文件的问题,这就是为什么不采用文件夹????存储小文件方案,很影响服务器的。

总结:通过学习现有的工具原理来进行改进创新还是很有帮助的。

原驼

    原驼:学名为Lama guanicoe,是一种温顺的食草动物,属于骆驼科,共有四个亚种,是家养骆驼和羊驼的野生祖先。它们在智利南部的“托雷斯德尔潘恩”国家公园生活了几千年,而在其他地方,它们的命运并不乐观。原驼是野生的驼马,以群族居住,驼群中包括了一匹主雄驼、几匹雌驼及它们的幼驼。主要吃草,原驼毛质地优良,手感极好。主要分布于阿根廷、玻利维亚、智利、巴拉圭和秘鲁。

    原驼在19世纪开始的数量急剧下降。栖息地丧失、娱乐狩猎和偷猎是主要威胁,过度捕猎、草场退化、家畜侵占栖息地和采掘活动,由于过度放牧致使土地荒漠化,是使牧草范围大大缩小,都对原驼的灭亡起到了显著作用。现在分布区域的磁片倾造成相对孤立的种群。在南方的一些种群是在局部或区域性甚至有灭绝的严重风险。在巴塔哥尼亚草原,来自于私人土地拥有者的压力越来越大,可能会导致剩余的高密度驼群的威胁。

列入《世界自然保护联盟》(IUCN)ver .:2008年哺乳纲红色名录——低危(LC)。

https://www.iucnredlist.org/species/11186/18540211

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值