本帖最后由 小fisher 于 2016-1-4 00:57 编辑
浅谈zip文件格式 要辨别一个文件的类型,通常是看它的后缀名,但这种方法不是100%靠得住。比如我们把一个.txt文件修改成.docx,双击的时候会看到word程序会提示一个错误信息。这是因为,不同的文件类型通常有自己特有的二进制结构,要正确读取出文件蕴含的信息,需要按照这种二进制结构的规定到特定的位置找到特定的字节或字节组合,并转换成它要表达的信息。 通常来说,一个文件中会储存一类或多类有意义的信息(比如文本、图像、音频视频等),在这里,我们把存储这些信息的字节称为“数据块”,这些数据块中的字节值是0-255之间的任意值,通常有相当大的随机性,程序无法通过这些字节判断出它们是否代表合法的文字、图像或其他类型,也无法判断这些数据是否完整、是否被人为篡改或错误输出。所以,要想让其他程序可以正确地解读这些信息,需要在数据块的外部(前部、后部或前后)添加一些额外的固定格式的结构,其中最常见的就是各种各样的header(头信息、标头)结构。通常header会含有以下内容: l 标识符,通常位于header结构的最前部,比如exe的前两个字节是十六进制的4D 5A (“MZ”),bmp的前两个字节是十六进制的42 4D(“BM”),而jpg文件的前4个字节是十六进制的FF D8 FF E0,同时第9-12个字节是十六进制的4A 46 49 46(“JFIF”); l 文件、结构、数据块的字节长度; l 下一个需要处理的结构的相对或绝对偏移量、字节长度; l 各文件类型特有的信息,比如图像的宽高值、颜色深度,文字的编码格式,音频的采样率等。 有些Header和数据块是一体的,即一个header对应一个数据块,我们合称之为一条“记录”,而有些Header在文件中只出现一次,相当于是这些记录的索引。如果把一个文件看作一本书,把文件中的各个数据块看作是各章节的内容,前面一类header可以看作是各章节的摘要,后面一类Header则可以看作是书的总目录。后面分析zip文件时,我们会反复用到这个类比。
回到Zip文件的话题,如果把a1.txt、a2.txt、a3.txt压缩到一个名为a.zip的文件中,压缩软件首先要做的一个工作就是读取这三个文件(“原文件”)中的全部字节,然后用某种压缩算法使字节数降低,形成三个数据块(假设为a1[]、a2[]、a3[]三个字节数组),这时它当然不能一股脑地把所有字节依次写到一个文件当中,那样,各数据块长度、原始文件名、压缩算法等信息会全部丢失,文件解压也就无从入手了。所以,压缩软件需要分别在这三个数据块前加个称为Local File Header的header结构,这个结构的长度为30 + n + m个字节,其中m,n是不确定的值,分别代表原始文件名和附加信息的长度,结构具体如下: 偏移量 | | | | | LocalFileHeader标识符,依次为50 4B 03 04,对应长整数&H04034B50,其中前两个字节对应AscII字符为PK,是Zip算法发明者Phil Katz的首字母缩写。 | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
对应的VBA数据类型为: Private Type LocalFileHeader LocalHeaderSignature AsLong 'HEX 50 4B 03 04 VersionNeeded As Integer GeneralPurposeBitFlag As Integer CompressionMethod As Integer LastModifyTime As Integer LastModifyDate As Integer CRC32 As Long CompressedSize As Long UncompressedSize As Long FileNameLength As Integer ExtraFieldLength As Integer End Type 注: 【1】这里的文件最后修改日期/时间是DOS日期/时间,可通过API函数DosDateTimeToFileTime和FileTimeToSystemTime转换为我们平时使用的日期数字。 【2】由于VBA的自定义类型中不能包含变长字节数组,所以上面的LocalFileHeader结构中未包含最后两个字段(文件名和附加信息)。 这样一来,通过在a1[]、a2[]、a3[]三个数据块前面分别添加h1、h2、h3三个header,压缩后的文件就有了初步的条理,相当于一本书中划分出了章节,并在每章的头部添加了摘要信息。 这时,如果创建一个a.zip文件,把h1、a1[]、h2、a2[]、h3、a3[]的字节内容依次写入a.zip中,之后是可以通过解析a.zip还原出a1.txt、a2.txt和a3.txt三个文件的(方法是先把a.zip的前30个字节读入到一个LocalFileHeader型的变量h1中,这时30 + h1. FileNameLength + h1. ExtraFieldLength即是数据块a1[]在压缩文件a.zip中的位置, h1.CompressedSize即是数据块a1[]的长度,从而获取到a1[]的全部字节,即a1.txt文件压缩后的全部字节。同时,30 + h1. FileNameLength + h1. ExtraFieldLength + h1.CompressedSize即是第二个LocalFileHeader在a.zip文件中的位置,依此类推,即可得到三个压缩后的数据块a1[]、a2[]、a3[],进而通过解压算法还原出a1.txt、a2.txt、a3.txt)。 然而,上面的方法虽然可行但并不完善,因为照上面的方法,如果用户只想解压出a3.txt,也要按照顺序从头分析a.zip文件,压缩包中文件不多时尚无大碍,文件一多就会影响到解压的效率了。想像一下你在阅读一本划分了章节但没有目录的大部头书籍,你就能体会这种文件格式设计的缺陷了。 |