自定义压缩文件系统实现

在现代软件开发中,数据压缩是一个常见的需求。无论是为了节省存储空间还是为了加快网络传输速度,压缩算法都扮演着重要的角色。在.NET环境下,虽然提供了如GZipStream和DeflateStream等压缩工具,但它们主要用于单个文件的压缩和解压缩。如果要将多个文件压缩成一个归档文件,就需要更多的工作,或者利用第三方库。本文将介绍如何在没有第三方库的情况下,完全在.NET中实现一个自己的归档文件系统。

最初的想法是创建一个包含ArchiveFile对象列表的类。每个ArchiveFile对象包含文件的详细信息和一个填充了文件内容的字节数组。通过序列化这个类并通过压缩流来存储和压缩数据。尽管实现了这个解决方案(在库中作为TinyArchive类保留),但后来发现它有一个致命的缺陷:整个对象和所有未压缩的数据都需要加载到内存中。这限制了归档文件的最大大小。

设计思路

回到设计板上,意识到不能使用之前编写的压缩二进制序列化代码来处理文件结构,因为它不能选择性地加载归档文件的部分。需要创建自己的。想要能够只读取归档中文件的详细信息,然后使用这些信息来选择性地读取特定部分的文件。也意识到,不能把所有的索引都放在文件的前面,因为这将使得向归档中添加更多文件变得非常困难。

最终,决定从包含下一部分归档长度的两个字节开始。下一部分将是压缩文件的详细信息:它的名称和在归档中的长度,紧接着是文件的压缩内容。下一个文件将以相同的方式添加。代码将读取前两个字节,将其转换为ushort,并使用该值指定从归档中读取的下一个字节块的长度,这些字节块是索引详细信息。从索引中,它得到压缩数据块的长度,它使用这个长度跳到下一个索引。

实现细节

通过这种方式,读者可以非常快速地对整个归档进行目录化。它构建了一个可搜索的索引,指定了归档中每个压缩文件的起始字节索引号和长度。提取压缩文件只是打开归档文件的文件流,定位到要提取的文件的索引位置,然后读取正确数量的字节。然后通过DeflateStream类将压缩字节扩展到原始大小。

删除文件的挑战

删除归档中的文件并不是最初应用程序的要求,但觉得这是使库完整的必要条件。在这方面遇到了很多麻烦,而且对解决方案并不满意。删除方法基本上会在临时文件位置创建当前归档的副本,跳过它被告知要删除的文件。然后删除原始文件并将副本移动到其位置。

示例应用程序

在附带的Visual Studio项目中提供了一个演示归档表单应用程序。它是一个快速、基本的多文件归档实现。可以创建归档,向其中添加文件,提取它们,删除它们。可以使用树形视图控件浏览归档中的文件。

代码使用

处理归档的主要类是StreamingArchiveFile。下面的代码展示了如何创建一个新的归档,并将一个文件夹中的文件添加到其中:

C# // 创建一个新的或访问一个现有的流归档文件: StreamingArchiveFile arc = new StreamingArchiveFile( @" C:\Temp\streamingArchive.arc" ); // 现在遍历特定目录中的文件,并将它们添加到归档中: foreach ( string fileName in Directory.GetFiles( @" C:\Temp\Test\" , " *.*" )) { // 添加文件 arc.AddFile( new ArchiveFile(fileName)); }

提取文件:这段代码展示了如何枚举归档中的文件,将它们提取到一个临时文件夹,并打开它们。

C# // 打开现有的归档文件: StreamingArchiveFile arc = new StreamingArchiveFile( @" C:\Temp\streamingArchive.arc" ); // 遍历归档中的文件: foreach ( string fileName in arc.FileIndex.IndexedFileNames) { // 写入文件的名称 Debug.Print( " 文件: " + fileName); // 提取文件: ArchiveFile file = arc.GetFile(fileName); // 将其保存到磁盘: String tempFileName = Path.GetTempPath() + " \\" + file.Name; file.SaveAs(tempFileName); // 打开文件: Process.Start(tempFileName); }

还有一个使用正则表达式和LINQ在归档中查找文件的搜索方法:

C# /// <summary> /// </summary> /// <param name="fileNameExpression" > </param > /// <returns > </returns > public IEnumerable<ArchiveFileIndex> Search( String fileNameExpression) { return ( from file in _fileIndex where Regex.IsMatch(file.FileName, fileNameExpression) select file); }

要真正了解归档的使用方式,请查看FrmStreamingArchiveUI表单中的代码。

.NET提供了两个压缩类:GZipStream和DeflateStream。它们都使用deflate算法,GZipStream类实际上是在DeflateStream之上构建的,并添加了一些额外的头部信息和CRC检查。使用了DeflateStream类,因为它更快,占用的空间更小。

C# /// <summary> /// </summary> /// <typeparam name="T" > 要反序列化的对象类型 </typeparam > /// <param name="compressedInputStream" > 包含要反序列化的对象的压缩数据的流 </param > /// <returns > </returns > public static T DeSerializeCompressed<T>(Stream compressedInputStream, bool useCustomBinder = false ) { // 构建二进制格式化程序并分配自定义绑定器: BinaryFormatter formatter = new BinaryFormatter(); if (useCustomBinder) formatter.Binder = new TinySerializer( typeof (T)); // 通过GZip解压缩流读取流。 using (DeflateStream decompressionStream = new DeflateStream(compressedInputStream, CompressionMode.Decompress, true )) { // 反序列化为对象: object graph = formatter.Deserialize(decompressionStream); // 检查类型是否正确并返回。 if (graph is T) return (T)graph; else throw new ArgumentException( " 无效类型! "); } }
沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485