web-dev-qa-db-ja.com

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(); 
} 

結果のストリームの長さは133バイトです

FiddlerのUtilities.GzipCompressまたは this PHP page のいずれかで同じ文字列を実行すると、結果はわずか31バイトの長さになります。

どちらの場合も入力は11バイトなので、PHPの結果は正しいと思いますが、明らかにこれはPHP Zip from .NET内またはその逆。NET出力が非常に大きいのはなぜですか。


実際、PHPとFiddlerの結果は同じ長さですが、同じではないことがわかりました。NETでPHPバージョンを解凍できます。 、ただしFiddlerバージョンではありません。PHPページは3つすべてを解凍するため、Fiddlerと.NETのgzipの実装の間に非互換性があるようです。


リクエストに応じて、3つの出力をdropboxにアップロードしました ここ

そして、これらはそれらのファイルの生の16進ダンプです(これらが実際にこのような用途であるかどうかはわかりませんが、フィドラーと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.{. J.J..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
32
Martin Harris

はじめに:。NETユーザーは、Microsoftが完全に機能するものに置き換えない限り、いかなる状況でもMicrosoft提供のGZipStreamまたはDeflateStreamクラスを使用しないでください。 。代わりに DotNetZipライブラリ を使用してください。

まえがきの更新:。NET Framework 4.5以降では圧縮の問題が修正されており、GZipStreamとDeflateStreamはこれらのバージョンでzlibを使用しています。以下で参照されているCRCの問題が修正されているかどうかはわかりません。

別の更新:CRCの問題は修正されただけでなく、Microsoftはそれらを 修正しない と決定しました。それ!

これは、GZipStreamのいくつかのバグの1つです。自尊心のあるgzipコンプレッサーは、11バイトの入力から133バイトの出力を決して生成してはなりません。 BCL GZipStream(StreamReaderを使用)がCRC32のデータエラーを確実に検出しないのはなぜですか? の私のコメントを参照してください。

内部で起こっていることは、GZipStreamが静的メソッドまたは保存されたメソッドを使用していないことです。どちらも入力データとほぼ同じサイズの圧縮データを生成します(その上に18バイトのgzipヘッダーとトレーラーが追加されます)。代わりに、非常に少数のコードに対して非常に大きなコード記述子ヘッダーを作成する動的メソッドを使用しています。それは単にバグ/非常に悪い実装です。

更新:

16進ダンプを使用して、いくつかの分析を提供できます。まず、Fiddlerとphpの両方の出力が正しく適切です。それらの唯一の違いは、gzipヘッダー、特にFiddlerで設定されているがphpでは設定されていないタイムスタンプと、元のオペレーティングシステムがphpで設定されているがFiddlerでは設定されていないことです。どちらの場合も、13バイトの圧縮データは同一であり、次のように表すことができます(my infgen program を使用してdeflateストリームを逆アセンブルします)。

last
static
literal 'Hello World
end

これはまさにあるべき姿です。コード記述子を必要とせず、すべてのバイトをリテラルとしてコーディングするだけの単一の静的ブロック。 (長さと距離を持つ以前の文字列の一致はありません。)

一方、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

それで、それは何ですか?実際のデータは、入力の各バイトをコード化するだけの「literal'HelloWorld」の終わり近くの行です。その前にあるのは、リテラル、長さ、および距離の一連のハフマンコードの説明です。これが間違っていることです:

  • まず、ダイナミックを使用するべきではありません。コードのセットを記述するのに約100バイトかかります。これが、deflate形式が静的ブロックで使用される事前定義されたコードのセットを提供する理由です。この場合、コンプレッサーは静的ブロックを選択する必要があります(これは、phpとFiddlerが行っていることです)。
  • 次に、大部分が使用されることはありませんが、考えられるすべてのコードが定義されています。動的ブロックを使用する場合、適切なコンプレッサーは、そのブロックで実際に使用されるリテラル、長さ、および距離のコードのみを定義します。この場合、長さや距離は使用されず、8つの異なるリテラル(H、e、l、o、スペース、w、r、およびd)のみが使用されます。代わりに、256のリテラルコード、29の長さコード、および30の距離コードの定義に進みます。いくつかの実験で、GZipStreamの動的ヘッダーが常に同じであることが示されると思います。その場合、動的でさえありません。これが要点です。
  • 第三に、最後に不要な空の格納ブロックをスローします。最初のブロックは最後のブロックとしてマークされている必要があります。

これはすべて、このGZipStreamコードを書いた人は誰でも、できる限り丁寧に言えば、デフレート形式や一般的な圧縮についての理解が不足しているという単純な事実を示しています。彼らは、動的ブロックのみを生成し(最後の空の静的ブロックを除く)、毎回同じ動的ヘッダーのみを生成し(私は思う)、動的ブロックの目的を破り、現在のブロックは最後のものであり、終わりをマークするために空のブロックを出す必要があります。

他の場所で述べたように、GZipStreamの問題はそれだけではありません。破損したストリームを検出する目的でCRC-32を適切に使用することさえできません。

本当に困惑するのは、Microsoftがgzipコンプレッサーとデコンプレッサーを書く能力のない人を割り当てた理由ではなく、それを書くために誰かを割り当てた理由です!自由に利用できるコード zlib があります。これは、帰属なしで商用利用を許可する非常に自由なライセンスを持っています。このコードは、ほぼ20年間広く展開されており、正しく効率的に実行するはずのすべてのことを実行します。 phpを含め、他のほとんどすべてがzlibを使用しており、Fiddlerも疑っています。

71
Mark Adler

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)。修正されたセクションの残りの部分は、@ mark-adlerによって説明されています BCL GZipStream(StreamReaderを使用)がCRC32でデータエラーを確実に検出しないのはなぜですか?

2
zeFrenchy

GZipStreamは、 RFC 1952仕様 で説明されているように、圧縮データに10バイトのヘッダーと8バイトのフッターを追加します。これにより、133バイトの長さの結果が得られます。

リンク先のPHPページでも、要求された場合は同じ18バイトのヘッダー/フッターが追加されます(GZIP-compatible encoding?)。これを使用すると、31バイトの長さの結果が得られます。

ヘッダー/フッターがない場合、それらの違いは125バイトと13バイトです。

2
Henrik Ripa

部分的に特許:

圧縮レベルが他のいくつかのアプリケーションほど良くない理由は、市場で最も効率的な圧縮アルゴリズムがすべて特許で保護されているためです。一方、.netは特許を取得していないものを使用します。

そして

同じことを尋ねたときに(MSの誰かから)私が得た説明は、MicrosoftがGZipアルゴリズムを変更せずに使用できないことに関係しているというものでした。特許/ライセンスの問題による。

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

最初は、Microsoftのgzip実装を疑っていました。彼らが最も効果的ではないが特許のないDeflateアルゴリズムを実装していることを私は知っていました。

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

0
Matthew1471