为什么我的C#gzip生成比Fiddler或PHP更大的文件?

如果我GZip这个文本:

你好,世界

通过C#使用这个代码:

Stream stream = new MemoryStream(Encoding.Default.GetBytes("Hello World")); var compressedMemoryStream = new MemoryStream(); using (var gzipStream = new GZipStream(compressedMemoryStream, CompressionMode.Compress)) { stream.CopyTo(gzipStream); gzipStream.Close(); } 

结果stream是133字节长

通过Fiddler的Utilities.GzipCompress或这个PHP页面运行相同的string,结果只有31个字节长。

在这两种情况下,input是11个字节,所以我可以想象PHP的结果是正确的,但显然这意味着我不能从.NET中解压缩PHP zip,反之亦然。 为什么.NET的输出更大?


事实上,PHP和Fiddler的结果是相同的,但它们并不相同。 我可以在.NET中解压缩PHP版本,但不能提供Fiddler版本。 PHP页面解压所有这三个,所以看起来好像在Fiddler和.NET的gzip实现之间可能是不兼容的。


按照要求,我已经在这里上传了三个输出到Dropbox。

这些是这些文件的原始hexdumps(不知道他们是否真的有这样的使用,但我认为这表明,提琴手和PHP版本之间的区别是在头,而不是压缩的数据本身):

小提琴手:

 0000-0010: 1f 8b 08 00-c2 e6 ff 4f-00 ff f3 48-cd c9 c9 57 .......O ...H...W 0000-001f: 08 cf 2f ca-49 01 00 56-b1 17 4a 0b-00 00 00 ../.I..V ..J.... 

PHP:

 0000-0010: 1f 8b 08 00-00 00 00 00-00 03 f3 48-cd c9 c9 57 ........ ...H...W 0000-001f: 08 cf 2f ca-49 01 00 56-b1 17 4a 0b-00 00 00 ../.I..V ..J.... 

C#:

 0000-0010: 1f 8b 08 00-00 00 00 00-04 00 ec bd-07 60 1c 49 ........ .....`.I 0000-0020: 96 25 26 2f-6d ca 7b 7f-4a f5 4a d7-e0 74 a1 08 .%&/m.{. JJ.t.. 0000-0030: 80 60 13 24-d8 90 40 10-ec c1 88 cd-e6 92 ec 1d .`.$..@. ........ 0000-0040: 69 47 23 29-ab 2a 81 ca-65 56 65 5d-66 16 40 cc iG#).*.. eVe]f.@. 0000-0050: ed 9d bc f7-de 7b ef bd-f7 de 7b ef-bd f7 ba 3b .....{.. ..{....; 0000-0060: 9d 4e 27 f7-df ff 3f 5c-66 64 01 6c-f6 ce 4a da .N'...?\ fd.l..J. 0000-0070: c9 9e 21 80-aa c8 1f 3f-7e 7c 1f 3f-22 be 9d 97 ..!....? ~|.?"... 0000-0080: 65 95 7e b7-aa cb d9 ff-13 00 00 ff-ff 56 b1 17 e.~..... .....V.. 0000-0085: 4a 0b 00 00-00 

前言:在任何情况下,.NET用户都不应该使用微软提供的GZipStream或者DeflateStream类,除非微软用一些有效的东西来完全替代它们。 改用DotNetZip库 。

更新前言: .NET Framework 4.5和更高版本已经解决了压缩问题,GZipStream和DeflateStream在这些版本中使用zlib。 我不知道下面提到的CRC问题是否已经解决了。

另一个更新: CRC问题不仅是不固定的,但微软已经决定,他们不会修复它!

这是GZipStream中的几个错误之一。 没有自尊的gzip压缩器应该从11个字节的input中产生133个字节的输出。 为什么BCL GZipStream(使用StreamReader)不能可靠地检测CRC32的数据错误? 。

内部发生的事情是,GZipStream没有使用静态或存储的方法,这两种方法都会产生与input数据大小相同的压缩数据(最后将增加18个字节的gzip头部和尾部)。 相反,它使用dynamic方法,它为很less的代码创build一个非常大的代码描述符头。 这只是一个错误/非常糟糕的实现。

更新:

随着hex转储,我可以提供一些分析。 首先,Fiddler和php的输出都是正确的。 它们之间的唯一区别是在gzip头,特别是在Fiddler中设置的时间戳,但不是在PHP中,以及原始的操作系统在PHP中设置,但不是在提琴手。 对于13个字节的压缩数据是相同的,可以表示为(使用我的infgen程序来反汇编deflatestream):

 last static literal 'Hello World end 

这完全是它应该的。 一个单独的静态块,它不需要代码描述符,只需将所有字节编码为文字。 (与长度和距离以前的string没有匹配。)

另一方面,GZipStream的输出在几个方面是一个可怕的混乱。 压缩的数据是:

 dynamic code 3 5 code 4 5 code 5 4 code 6 4 code 7 4 code 8 3 code 9 3 code 10 4 code 11 4 code 12 4 code 13 4 code 14 3 code 16 3 litlen 0 14 litlen 1 14 litlen 2 14 litlen 3 14 litlen 4 14 litlen 5 14 litlen 6 14 litlen 7 14 litlen 8 14 litlen 9 12 litlen 10 6 litlen 11 14 litlen 12 14 litlen 13 14 litlen 14 14 litlen 15 14 litlen 16 14 litlen 17 14 litlen 18 14 litlen 19 14 litlen 20 14 litlen 21 14 litlen 22 14 litlen 23 14 litlen 24 14 litlen 25 14 litlen 26 14 litlen 27 14 litlen 28 14 litlen 29 14 litlen 30 13 litlen 31 14 litlen 32 6 litlen 33 14 litlen 34 10 litlen 35 12 litlen 36 14 litlen 37 14 litlen 38 13 litlen 39 10 litlen 40 8 litlen 41 9 litlen 42 11 litlen 43 10 litlen 44 7 litlen 45 8 litlen 46 7 litlen 47 9 litlen 48 8 litlen 49 8 litlen 50 8 litlen 51 9 litlen 52 8 litlen 53 9 litlen 54 10 litlen 55 9 litlen 56 8 litlen 57 9 litlen 58 9 litlen 59 8 litlen 60 9 litlen 61 10 litlen 62 8 litlen 63 14 litlen 64 14 litlen 65 8 litlen 66 9 litlen 67 8 litlen 68 9 litlen 69 8 litlen 70 9 litlen 71 10 litlen 72 11 litlen 73 8 litlen 74 11 litlen 75 14 litlen 76 9 litlen 77 10 litlen 78 9 litlen 79 10 litlen 80 9 litlen 81 12 litlen 82 9 litlen 83 9 litlen 84 9 litlen 85 10 litlen 86 12 litlen 87 11 litlen 88 14 litlen 89 14 litlen 90 12 litlen 91 11 litlen 92 14 litlen 93 11 litlen 94 14 litlen 95 14 litlen 96 14 litlen 97 6 litlen 98 7 litlen 99 7 litlen 100 7 litlen 101 6 litlen 102 8 litlen 103 8 litlen 104 7 litlen 105 6 litlen 106 12 litlen 107 9 litlen 108 6 litlen 109 7 litlen 110 7 litlen 111 6 litlen 112 7 litlen 113 13 litlen 114 6 litlen 115 6 litlen 116 6 litlen 117 7 litlen 118 8 litlen 119 8 litlen 120 9 litlen 121 8 litlen 122 11 litlen 123 13 litlen 124 12 litlen 125 13 litlen 126 13 litlen 127 14 litlen 128 14 litlen 129 14 litlen 130 14 litlen 131 14 litlen 132 14 litlen 133 14 litlen 134 14 litlen 135 14 litlen 136 14 litlen 137 14 litlen 138 14 litlen 139 14 litlen 140 14 litlen 141 14 litlen 142 14 litlen 143 14 litlen 144 14 litlen 145 14 litlen 146 14 litlen 147 14 litlen 148 14 litlen 149 14 litlen 150 14 litlen 151 14 litlen 152 14 litlen 153 14 litlen 154 14 litlen 155 14 litlen 156 14 litlen 157 14 litlen 158 14 litlen 159 14 litlen 160 14 litlen 161 14 litlen 162 14 litlen 163 14 litlen 164 14 litlen 165 14 litlen 166 14 litlen 167 14 litlen 168 14 litlen 169 14 litlen 170 14 litlen 171 14 litlen 172 14 litlen 173 14 litlen 174 14 litlen 175 14 litlen 176 14 litlen 177 14 litlen 178 14 litlen 179 14 litlen 180 14 litlen 181 14 litlen 182 14 litlen 183 14 litlen 184 14 litlen 185 14 litlen 186 14 litlen 187 14 litlen 188 14 litlen 189 14 litlen 190 14 litlen 191 14 litlen 192 14 litlen 193 14 litlen 194 14 litlen 195 14 litlen 196 14 litlen 197 14 litlen 198 14 litlen 199 14 litlen 200 14 litlen 201 14 litlen 202 14 litlen 203 14 litlen 204 14 litlen 205 14 litlen 206 14 litlen 207 14 litlen 208 14 litlen 209 14 litlen 210 14 litlen 211 14 litlen 212 14 litlen 213 14 litlen 214 14 litlen 215 14 litlen 216 14 litlen 217 14 litlen 218 14 litlen 219 14 litlen 220 14 litlen 221 14 litlen 222 14 litlen 223 14 litlen 224 14 litlen 225 14 litlen 226 14 litlen 227 14 litlen 228 14 litlen 229 14 litlen 230 14 litlen 231 14 litlen 232 14 litlen 233 14 litlen 234 14 litlen 235 14 litlen 236 14 litlen 237 14 litlen 238 14 litlen 239 14 litlen 240 14 litlen 241 14 litlen 242 14 litlen 243 13 litlen 244 13 litlen 245 13 litlen 246 14 litlen 247 13 litlen 248 14 litlen 249 13 litlen 250 14 litlen 251 13 litlen 252 14 litlen 253 14 litlen 254 14 litlen 255 14 litlen 256 14 litlen 257 4 litlen 258 3 litlen 259 4 litlen 260 4 litlen 261 4 litlen 262 5 litlen 263 5 litlen 264 5 litlen 265 5 litlen 266 5 litlen 267 6 litlen 268 6 litlen 269 5 litlen 270 6 litlen 271 7 litlen 272 8 litlen 273 8 litlen 274 9 litlen 275 10 litlen 276 9 litlen 277 10 litlen 278 12 litlen 279 11 litlen 280 12 litlen 281 14 litlen 282 14 litlen 283 14 litlen 284 12 litlen 285 11 dist 0 6 dist 1 10 dist 2 11 dist 3 11 dist 4 9 dist 5 8 dist 6 8 dist 7 8 dist 8 7 dist 9 7 dist 10 5 dist 11 6 dist 12 4 dist 13 5 dist 14 4 dist 15 5 dist 16 4 dist 17 5 dist 18 4 dist 19 4 dist 20 4 dist 21 4 dist 22 4 dist 23 4 dist 24 4 dist 25 5 dist 26 4 dist 27 5 dist 28 5 dist 29 5 literal 'Hello World end ! last stored end 

那么这是什么? 实际的数据只是接近“文字”“Hello World”的行,它只对input的每个字节进行编码。 之前的是一组霍夫曼码的文字,长度和距离的描述。 以下是错误的事情:

  • 首先,它不应该使用dynamic。 描述这组代码大约需要100个字节。 这正是deflate格式提供静态块中使用的预定义代码集的原因。 在这种情况下,压缩机应该select一个静态块(这是php和Fiddler所做的)。
  • 其次,每一个可能的代码都被定义了,尽pipe绝大多数都是从来没有使用的! 当使用dynamic块时,一个合适的压缩器将只定义在该块中实际使用的文字,长度和距离的代码。 在这种情况下,没有使用长度或距离,只使用了8个不同的文字(H,e,l,o,space,w,r和d)。 而是继续定义256个文字代码,29个长度代码和30个距离代码。 我猜测一些实验会显示GZipStream中的dynamic头部总是相同的,在这种情况下,它甚至不是dynamic的,这就是整个问题!
  • 第三,它在最后抛出一个不必要的空存储块。 第一个块应该被标记为最后一个块。

所有这一切都表明了这样一个简单的事实,那就是编写这个GZipStream代码的人,尽可能礼貌地说,对于压缩格式或压缩总体上没有任何理解。 他们select只产生dynamic块(除了最后一个空的静态块),每次只产生相同的dynamic头(我认为),破坏了dynamic块的目的,并且不费心去计算出当前块是最后一块,需要把空块标出来结束。

正如其他地方所指出的那样,这些并不是GZipStream的唯一问题。 它甚至不能正确使用CRC-32来检测损坏的数据stream。

真正令人困惑的事情不是为什么微软指派了一个无能的人写一个gzip压缩器和解压缩器,而是为什么他们指派任何人写它! 有免费的代码, zlib ,有一个非常自由的许可证,允许商业用途没有归属。 这个代码已经被广泛部署了近二十年,并且做了所有它应该做的正确而有效的事情。 大多数其他所有使用zlib,包括PHP和我怀疑提琴手以及。

GZipStream RFC 1952规范的描述, GZipStream将一个10字节的标题和一个8字节的页脚添加到压缩的数据中。 这给出了一个133字节的结果。

如果要求( GZIP-compatible encoding? ),链接到的PHP页面也会添加相同的18字节页眉/页脚。 如果你使用它,结果是31个字节长。

没有页眉/页脚,它们之间的差异是125比13字节。

无论您将什么内容添加到GZipStream中,都会产生相同的开销。 GZipStream的前108个字节看起来是一样的

 1f 8b 08 00 00 00 00 00 04 00 ec bd 07 60 1c 49 96 25 26 2f 6d ca 7b 7f 4a f5 4a d7 e0 74 a1 08 80 60 13 24 d8 90 40 10 ec c1 88 cd e6 92 ec 1d 69 47 23 29 ab 2a 81 ca 65 56 65 5d 66 16 40 cc ed 9d bc f7 de 7b ef bd f7 de 7b ef bd f7 ba 3b 9d 4e 27 f7 df ff 3f 5c 66 64 01 6c f6 ce 4a da c9 9e 21 80 aa c8 1f 3f 7e 7c 1f 3f 22 >>> 

最多1f 8b 08 00 00 00 00 00 04 00符合标准定义(ttp://www.faqs.org/rfcs/rfc1952.html)。 为什么BCL GZipStream(使用StreamReader)不能可靠地检测CRC32的数据错误?

部分专利:

压缩级别不如其他应用程序的原因是市场上最有效的压缩algorithm都受到专利保护。 .net另一方面使用一个非专利的。

那么,当我问到同样的问题时,我得到的解释(从MS的某个人那里得到)是因为微软不能在不修改GZipalgorithm的情况下使用GZipalgorithm。 由于专利/许可问题。

http://social.msdn.microsoft.com/Forums/fr-FR/c5f0b53c-a2d5-4407-b43b-9da8d39c01df/why-do-gzipstream-compression-ratio-so-bad?forum=netfxbcl

最初我怀疑微软的gzip实现; 我知道他们实施的Deflatealgorithm不是最有效的,但没有专利。

http://challenge-me.ws/post/2010/11/05/Do-Not-Take-Microsofts-Code-for-Granted.aspx

现代压缩工具通常会使用多个压缩策略。 与Winzip和WinRAR等,你通常会得到如下选项:

  • 最大压缩
  • 最快的
  • 正常

如果你这样做,你可能会进一步压缩文件。