李懿 Excel学习 2016-07-05
微信扫一扫
关注该公众号
需要之前的文章,请在公众号内回复:
复合文档
5、读取DIFAT,FAT,MINIFAT
上一次,我们成功读取了文件头Header。这一次,我们要根据Header中的信息读取DIFAT,FAT和MINIFAT。这三个信息至关重要,它直接关系到我们数据真正的存储顺序。在那之前,先来复习并扩展一下这些基本的概念。
5.1 文件和扇区
是否还记得第一部分我们介绍的基础知识呢?数据存储是以扇区为单位的,对于文件也是一样。一个文件可能占用多个扇区。对于复合文档,每个不同类型的数据都是单独存放在不同扇区的,换句话说,一个扇区内只存放一种类型的数据。有些扇区存放FAT信息,有些存放DIFAT信息,有些则存放普通的文件的数据。
22 文件和扇区示例
如上图所示,除去文件头外,每个扇区从开始到最后都有一个从0开始的数字编号。若要读取某个扇区的信息,我们要获取扇区相对于文件开头的偏移量,可以采用以下的公式:
扇区ID x 扇区大小 + 512
其中512是文件头占用的空间,文件头之后的0号扇区的偏移量就是0*512+512=512,依此类推。
5.2 扇区ID
前文所述一个扇区只会存放一种类型的数据,因而在复合文档的FAT数据中,除了表示扇区ID的数字,还有些特殊的数字ID表示一些特定的扇区。
图 23 扇区ID类型
为了方便使用,根据扇区类型,可以声明一些枚举值如下:
Public Enum SECTORTYPE
MAXREGSECT = &HFFFFFFFA
NASECT = &HFFFFFFFB
DIFSECT = &HFFFFFFFC
FATSECT = &HFFFFFFFD
ENDOFCHAIN = &HFFFFFFFE
FREESECT = &HFFFFFFFF
End Enum
5.3 FAT
FAT中记录所有扇区的详细信息,包括扇区的类型,数据扇区的顺序等等。
可以把FAT当成一个数组,数组中存储的都是Long类型的整数(4个字节),则其中的每个整数代表一个扇区的ID,代表了数据区域下一个扇区的ID。我们用一个下标起始为0(与扇区ID同步)的数组来表示FAT,来看一个具体的示例。
图 24 FAT示例
上图对应了之前的那个文件FAT[0]表示了0号扇区,这个扇区以及4、5号扇区存储了FAT的信息,因而其中存储了十六进制数0xFFFFFFFD。3、6号扇区为DIFAT扇区,因而其中存储了十六进制数0xFFFFFFFC。扇区1、2、N存放了正常的文件数据,因而其数字表示的是该扇区的下一个数据扇区的ID,比如与1号扇区连续的下一个数据扇区是2号,与2号扇区连续的下一个数据扇区是7号。而在N号扇区的FAT中存放了0xFFFFFFFE,表示这个扇区之后没有更多数据扇区。
通常一个扇区大小为512字节,因而一个FAT扇区中,最多可以存放128组FAT信息。在一个扇区内部,这些FAT的信息都是连续的,即FAT所表示的扇区都是连续的。比如某个FAT扇区,第1个FAT代表的扇区的ID是51的话,则这个扇区可以表示ID从51到178的扇区。
要把不同的FAT扇区串联起来的方法则是通过DIFAT列表。
5.4 DIFAT
DIFAT是FAT扇区的索引,表示了FAT扇区的前后顺序。DIFAT和FAT类似,也可以用数组表示,其存储值表示了FAT扇区的正确读取顺序。
除了Header中存储的109个DIFAT数据,还可以使用其它扇区专门存储DIFAT数据。作为DIFAT扇区,由于其可能有存在多个扇区组合,并且它没有额外的索引表示其顺序,因而其存储的数据不像FAT扇区那样全部都用于存储FAT数据。在DIFAT扇区的最后4个字节,存储了下个DIFAT扇区的ID,以表示下个DIFAT数据该去哪儿找。
图25 DIFAT扇区示意
5.5 使用VBA解析DIFAT
前109个DIFAT存储在Header之中,若DIFAT超过109个,则在Header中可以通过DIFATSectorStart读取第一个DIFAT扇区的ID,然后读取该扇区的127个DIFAT信息。最后一个Long类型的数据即为下一个DIFAT扇区的ID。
我们添加以下代码:
'读取DIFAT
Sub ReadDIFAT(FS As Integer, Hdr As CFBHeader, DIFAT() As Long, SectorSize As Long)
Dim i As Long
'先读取Header中的DIFAT
For i = 0 To 108
'遇到结束ID,则跳出
If Hdr.DIFAT(i) = SECTORTYPE.ENDOFCHAIN Or Hdr.DIFAT(i) = SECTORTYPE.FREESECT Then
Exit Sub
End If
'添加DIFAT
AddItem2List DIFAT, Hdr.DIFAT(i)
Next i
'若还有其他DIFAT扇区,则继续读取
If Hdr.DIFATSectorCount > 0 Then _
ReadDIFATSector FS, DIFAT,Hdr.DIFATSectorStart, SectorSize
End Sub
'读取指定扇区的DIFAT数据
Sub ReadDIFATSector(FS As Integer,DIFAT() As Long, SectorID As Long, SectorSize As Long)
Dim i As Long
Dim ID As Long
'定位
Seek FS, GetFileOffset(SectorID, SectorSize)
'读取DIFAT。在一个DIFAT扇区中,最多有127个DIFAT数据,最后一个为下一个DIFAT扇区的ID
For i = 0 To 126
'读取数据
Get FS, , ID
'遇到结束ID,则跳出
If ID = SECTORTYPE.ENDOFCHAIN Or ID = SECTORTYPE.FREESECT Then
Exit Sub
End If
'添加DIFAT
AddItem2List DIFAT, ID
Next i
'读取下一个DIFAT扇区的ID
Get FS, , ID
'采用递归,调用自己继续读取
ReadDIFATSector FS, DIFAT, ID, SectorSize
End Sub
首先用ReadDIFAT方法,来读取Header中的前109个DIFAT信息,然后判断是否存在DIFAT扇区,若存在,则调用方法读取该扇区。在ReadDIFATSector中,顺序读取前127个DIFAT信息,然后读取最后一个信息,即为下一个DIFAT扇区的位置,然后调用方法本身继续读取。
注:代码中使用了一个AddItem2List的方法,把数据添加到数组中。
5.6 使用VBA解析FAT
解析FAT的程序与解析类似,唯一不同的是,FAT扇区的读取顺序是由DIFAT数组决定的。所以在读取FAT的时候,需要以此遍历DIFAT数组,到指定的FAT扇区中读取FAT数据。
参考代码如下:
'读取FAT
Sub ReadFat(FS As Integer, DIFAT() As Long, FAT() As Long, SectorSize As Long)
Dim i As Long
'根据DIFAT的顺序。读取每个FAT扇区
For i = 0 To UBound(DIFAT)
ReadFATSector FS, FAT(), DIFAT(i),SectorSize
Next i
End Sub
'读取指定扇区的FAT
Sub ReadFATSector(FS As Integer,FAT() As Long, SectorID As Long, SectorSize As Long)
Dim i As Long
Dim ID As Long
'定位
Seek FS, GetFileOffset(SectorID, SectorSize)
'读取FAT。在一个FAT扇区中,有128个FAT数据
For i = 0 To 127
'读取数据
Get FS, , ID
'添加至FAT
AddItem2List FAT, ID
Next i
End Sub
5.7 使用VBA解析MINIFAT
至于解析MINIFAT,留给大家自己完成吧。其方法和FAT类似,其起始扇区ID存放在Header信息中,至于顺序嘛,当然是存在FAT中哦,不能搞错哦。
5.8 小结
本章的内容至关重要,直接关系到能否读取正确的数据。后面,我将来介绍如何用FAT信息和MINIFAT信息正确读取数据。
5.9 示例代码下载
如果你懒得写代码,那么可以直接下载附件。点击【阅读原文】即可。我在文件中准备了两个不同的文件供测试,一个大的文件是有DIFAT扇区的,一个则没有。大家可以思考一下,什么样的文件才会有DIFAT扇区?具备DIFAT扇区的文件有什么特征呢?