浅谈在c#中使用Zlib压缩与解压的方法
作者:Compasslg
介绍
近期用c#开发一个游戏的存档编辑工具需要用 Zlib 标准的 Deflate 算法对数据进行解压。 在 StackOverflow 上逛了一圈,发现 c# 比较常用到的方式是微软提供的 System.IO.Compression, zlib.net, 以及 ICSharpCode 的SharpZipLib。我简单的测试和包装了一下,便在这里分享一下成果以及我个人的看法。
System.IO.Compression
通常来说,使用c#开发时能用微软官方提供的工具就尽量用,一个是bug会比较少,维护会比较稳定。此外,官方提供的方案往往在优化上也会高于第三方工具。
虽然在.NET Framework 4.5 开始 System.IO.Compression.DeflateStream 也使用Zlib进行Deflate格式的压缩与解压了,但经过测试其压缩和解压结果与其他Zlib库有所不同.
仔细观察就会发现,用 DeflateStream 压缩后的数据开头比Zlib压缩的数据少两个字节,结尾比Zlib少四个字节; 这种输出格式叫做 Raw Deflate 。
经过查证,C# 提供的 DeflateStream只能压缩成或者解压这种Raw Deflate, 而不能处理标准的 Zlib Deflate 格式 (不过据说可以自己生成); 但反过来,Zlib 可以处理或生成这种不包含头尾数据的Raw Deflate.
当然,你也可以选择手动添加 header 和 trailer. 具体怎么添加可以阅读文末链接的参考资料,由于不是特别重要,我就偷个懒了。
以下是我使用此方法简单包装的压缩与解压数据的代码:
// 使用System.IO.Compression进行Deflate压缩
public static byte[] MicrosoftCompress(byte[] data)
{
    MemoryStream uncompressed = new MemoryStream(data); // 这里举例用的是内存中的数据;需要对文本进行压缩的话,使用 FileStream 即可
    MemoryStream compressed = new MemoryStream();
    DeflateStream deflateStream = new DeflateStream(compressed, CompressionMode.Compress); // 注意:这里第一个参数填写的是压缩后的数据应该被输出到的地方
    uncompressed.CopyTo(deflateStream); // 用 CopyTo 将需要压缩的数据一次性输入;也可以使用Write进行部分输入
    deflateStream.Close();  // 在Close中,会先后执行 Finish 和 Flush 操作。
    byte[] result = compressed.ToArray();
    return result;
}
// 使用System.IO.Compression进行Deflate解压
public static byte[] MicrosoftDecompress(byte[] data)
{
    MemoryStream compressed = new MemoryStream(data);
    MemoryStream decompressed = new MemoryStream();
    DeflateStream deflateStream = new DeflateStream(compressed, CompressionMode.Decompress); // 注意: 这里第一个参数同样是填写压缩的数据,但是这次是作为输入的数据
    deflateStream.CopyTo(decompressed);
    byte[] result = decompressed.ToArray();
    return result;
}
zlib.net
zlib.net是一个非常小体量的开源的第三方工具。经过本人有限的研究和了解,这个库其实更像是一个半成品,许多功能都不完善,不过优点在于非常轻巧,而且与c++端使用 boost::iostreams::zlib 效果相同。
以下是用 zlib.net 提供的 ZOutputStream 类来压缩数据的代码
public static byte[] ZLibDotnetCompress(byte[] data)
{
    MemoryStream compressed = new MemoryStream();
    ZOutputStream outputStream = new ZOutputStream(compressed, 2);
    outputStream.Write(data, 0, data.Length); // 这里采用的是用 Write 来写入需要压缩的数据;也可以采用和上面一样的方法
    outputStream.Close();
    byte[] result = compressed.ToArray();
    return result;
}
以下是用zlib.net 提供的 ZInputStream 类来解压数据的代码
public static byte[] ZLibDotnetDecompress(byte[] data, int size)
{
    MemoryStream compressed = new MemoryStream(data);
    ZInputStream inputStream = new ZInputStream(compressed);
    byte[] result = new byte[size];   // 由于ZInputStream 继承的是BinaryReader而不是Stream, 只能提前准备好输出的 buffer 然后用 read 获取定长数据。
    inputStream.read(result, 0, result.Length); // 注意这里的 read 首字母是小写
    return result;
}
你需要通过read来获取解压后的数据,同时你要在调用其解压的方法时提前提供好外部的buffer用于储存输出的数据,这个buffer的大小就是一个问题了。
如果打算使用这个的话,建议除了储存压缩的数据以外,在不会被压缩的位置添加压缩前大小的数据。
但总体来说,个人不建议使用这个工具。
https://github.com/zyborg/zlib.net
http://www.componentace.com/zlib_.NET.htm
SharpZipLib
我最终选择使用的是 SharpZipLib. (编辑:当时没做速度测试,且我需要解压的文件不是太大,速度也不是很重要,否则的话不推荐选择这个方案。。。)
ICSharpCode 不愧是开发了 ILSpy 的团队,SharpZipLib 在提供强大的功能的同时,使用也很方便。限于主题,这里只讨论用 Deflate 格式来压缩数据流。
简单来说,你需要做的就是通过 DeflaterOutputStream 来压缩,InflaterInputStream 来解压,而除了压缩和解压分在两个不同的类以外,其他的操作方式和 System.IO.Compression.DeflateStream 可以做到完全一样。
而且其压缩和解压的结果和直接使用Zlib官方的库一模一样,开发辅助其他程序的工具时不用担心头尾数据的问题,算是非常省事了。
以下是我使用该方案简单包装的方法:
public static byte[] SharpZipLibCompress(byte[] data)
{
    MemoryStream compressed = new MemoryStream();
    DeflaterOutputStream outputStream = new DeflaterOutputStream(compressed);
    outputStream.Write(data, 0, data.Length);
    outputStream.Close();
    return compressed.ToArray();
}
public static byte[] SharpZipLibDecompress(byte[] data)
{
    MemoryStream compressed = new MemoryStream(data);
    MemoryStream decompressed = new MemoryStream();
    InflaterInputStream inputStream = new InflaterInputStream(compressed);
    inputStream.CopyTo(decompressed);
    return decompressed.ToArray();
}
速度对比
为了对比几种方法在压缩与解压效率上的优劣,我准备了两组数据做了一个简单的测试。
第一组为短数据,是一个简单的字符串 "this is just a string for testing, see how this compression thing works."
第二组为长数据,是在网上下载到的英文版的 《冰与火之歌:权利的游戏》txt文本,大小约1.7mb。
我分别用每个方法压缩和解压短数据1000次,长数据100次, 最终的结果如下:
Length of Short Data: 144
Length of Long Data: 1685502
============================================
Compress and decompress with Microsoft Zlib Compression (1000 times): 54
Compress and decompress with Microsoft Zlib Compression (long data 100 times): 7924
============================================
Compress and decompress with Zlib.net Compression (1000 times): 254
Compress and decompress with Zlib.net Compression (long data 100 times): 9924
============================================
Compress and decompress with SharpZipLib Compression (1000 times): 442
Compress and decompress with SharpZipLib Compression (long data 100 times): 26782
显而易见的,无论是长数据还是短数据的压缩与解压,System.IO.Compression中提供的方法都优于另外两种方法。
Zlib.net在速度上的劣势不明显,而同样的算法SharpZipLib要花两到三倍的时间。
总结
最终,不出所料的,微软官方提供的 System.IO.Compression 中的方法在速度上有着明显的优势;虽然不会提供Deflate的头尾信息,但可以想办法自己生成,而且这一缺点基本上是可以完全忽略的。 Zlib.net 虽然在速度上表现也不错,同时也会生成Deflate压缩的头尾信息,但因为其包装比较潦草,使用起来相对不方便。而 SharpZipLib 很可惜,虽然其他各方面都很方便,但速度上的缺陷相当致命,只能在一定需要 Deflate 而非 RawDeflate 或者使用的.Net Framework早于4.5的时候(且运行中时间消耗不重要)偷懒的用一用了。
参考与延申
关于Zlib
关于 Deflate 和 Raw Deflate
https://stackoverflow.com/questions/37845440/net-deflatestream-vs-linux-zlib-difference
https://www.ietf.org/rfc/rfc1950.txt
https://www.ietf.org/rfc/rfc1951.txt
关于CSharp System.IO.Compression.DeflateStream
https://docs.microsoft.com/en-us/dotnet/api/system.io.compression.deflatestream?view=net-5.0
开发者之一 Mark Adler 在 StackOverflow 上的回答
deflate 和 compress 函数的区别
https://stackoverflow.com/questions/10166122/zlib-differences-between-the-deflate-and-compress-functions/10168441#10168441
如何手动添加 header 和 trailer
https://stackoverflow.com/questions/39939869/data-format-for-system-io-compression-deflatestream
浅谈在c#中使用Zlib压缩与解压的方法的更多相关文章
- python用模块zlib压缩与解压字符串和文件的方法
		
摘自:http://www.jb51.net/article/100218.htm Python标准模块中,有多个模块用于数据的压缩与解压缩,如zipfile,gzip, bz2等等. python中 ...
 - HDFS中文件的压缩与解压
		
HDFS中文件的压缩与解压 文件的压缩有两大好处:1.可以减少存储文件所需要的磁盘空间:2.可以加速数据在网络和磁盘上的传输.尤其是在处理大数据时,这两大好处是相当重要的. 下面是一个使用gzip工具 ...
 - Asp.net中文件的压缩与解压
		
这里笔者为大家介绍在asp.net中使用文件的压缩与解压.在asp.net中使用压缩给大家带来的好处是显而易见的,首先是减小了服务器端文件存储的空间,其次下载时候下载的是压缩文件想必也会有效果吧,特别 ...
 - python  模块zlib 压缩与解压
		
例子1:压缩与解压字符串 import zlib message = 'abcd1234' compressed = zlib.compress(message) decompressed = zli ...
 - Delphi Base64编码_解码及ZLib压缩_解压(转)
		
最近在写的程序与SOAP相关,所以用到了一些Base64编码/解码及数据压缩/解压方面的知识. 在这里来作一些总结:一.Base64编码/解码 一般用到的是Delphi自带的单元EncdDecd,当然 ...
 - Linux中下载,压缩,解压等命令
		
查看是否和还有一台Linux机器相通命令:ssh 主机名@Ip地址 ,提示输入password.就可以查看远程文件的文件夹 下载远程机器上的文件:scp 主机名@Ip地址:/path/s ...
 - 浅谈人脸识别中的loss 损失函数
		
浅谈人脸识别中的loss 损失函数 2019-04-17 17:57:33 liguiyuan112 阅读数 641更多 分类专栏: AI 人脸识别 版权声明:本文为博主原创文章,遵循CC 4.0 ...
 - 转: 浅谈C/C++中的指针和数组(二)
		
转自:http://www.cnblogs.com/dolphin0520/archive/2011/11/09/2242419.html 浅谈C/C++中的指针和数组(二) 前面已经讨论了指针和数组 ...
 - 转:浅谈C/C++中的指针和数组(一)
		
再次读的时候实践了一下代码,结果和原文不一致 error C2372: 'p' : redefinition; different types of indirection 不同类型的间接寻址 /// ...
 
随机推荐
- SpringBoot2.2.5整合ElasticSearch7.9.2
			
1:前言 为什么是SpringBoot2.2.5,不是其他的SpringBoot版本,原因有两个: 1:SpringBoot2.2.0以上才能支持ElasticSearch7.x版本. 2:Sprin ...
 - Guava-RateLimiter实现令牌桶控制接口限流方案
			
一.前言 对于一个应用系统来说,我们有时会遇到极限并发的情况,即有一个TPS/QPS阀值,如果超了阀值可能会导致服务器崩溃宕机,因此我们最好进行过载保护,防止大量请求涌入击垮系统.对服务接口进行限流可 ...
 - 资源授权?对OAuth2.0的一次重新认识的过程
			
什么是OAuth? OAuth一个开放的授权标准,允许用户在不提供关键信息(如账号,密码)给第三方应用的前提下,让第三方应用去访问用户在某网站上的资源(如头像,用户昵称等). OAuth分为OAuth ...
 - C++ Primer Plus 第一章 预备知识
			
C++ Primer Plus 第一章 预备知识 知识点梳理 本章主要讲述了C++的由来,讨论了面向过程语言与面向对象语言的区别,介绍了ANSI/ISO制定的C++标准,阐述了在Windows.Mac ...
 - wxWidgets源码分析(6) - 窗口关闭过程
			
目录 窗口关闭过程 调用流程 关闭文档 删除视图 删除文档对象 关闭Frame App清理 多文档窗口的关闭 多文档父窗口关闭 多文档子窗口关闭 窗口的正式删除 窗口关闭过程总结 如何手工删除view ...
 - Linux流量查看工具
			
目录 监控总体带宽使用 nload.bmon.slurm.bwm-ng.cbm.speedometer和netload 监控总体带宽使用(批量式输出) vnstat.ifstat.dstat和coll ...
 - 看完我的笔记不懂也会懂----ECMAscript 567
			
目录 ECMAscript 567 严格模式 字符串扩展 数值的扩展 Object对象方法扩展 数组的扩展 数组方法的扩展 bind.call.apply用法详解 let const 变量的解构赋值 ...
 - C# ref and out
			
相同点: 1. ref 和 out 都是按地址传递的,使用后都将改变原来参数的数值: 2. 方法定义和调用方法都必须显式使用 ref 或者 out关键字: 3. 通过ref 和 ref 特性,一定程度 ...
 - 阅读源码,HashMap回顾
			
目录 回顾 HashMap简介 类签名 常量 变量 构造方法 tableSizeFor方法 添加元素 putVal方法 获取元素 getNode方法 总结 本文一是总结前面两种集合,补充一些遗漏,再者 ...
 - JDBC 连接池 & Template
			
数据库连接池 # 概念:其实就是一个容器(集合),存放数据库连接的容器. * 当系统初始化号以后,容器被创建,容器中会申请一些连接对象,当用户来访问数据库时,从容其中获取连接对象,用户访问完之后,会将 ...