LoginSignup
6
5

JavaScript でデータを Deflate (Zlib, Gzip) 圧縮および解凍する

Last updated at Posted at 2023-03-16

本記事では以下の 2 種類の方法を書きます:

  • Compression Streams API を用いる方法
  • ライブラリ pako を用いる方法

1. 予備知識

1.1. Deflate 圧縮のデータ形式について

Deflate 圧縮のデータ形式は複数あり、主に以下のものが使用されています:

  • 生の deflate 形式 (RFC 1951)
  • zlib 形式 (RFC 1950)
    • 生の deflate 形式のデータにヘッダとフッタを付け加えた形式
    • ※単に「deflate 形式」と言う場合は「生の deflate 形式」でなく「zlib 形式」を指す場合があるため注意
  • gzip 形式 (RFC 1952)
    • 生の deflate 形式のデータにヘッダとフッタを付け加えた形式

参考「RFC 1950: ZLIB Compressed Data Format Specification version 3.3
参考「RFC 1951: DEFLATE Compressed Data Format Specification version 1.3
参考「RFC 1952: GZIP file format specification version 4.3

1.2. Compression Streams API について

Compression Streams API は deflate アルゴリズムでデータの圧縮や解凍を行える API で、この API が使用できる環境であれば別途ライブラリ等を利用しなくても圧縮および解凍できます。

ただし、2023-03-15 現在は以下のような一部の WEB ブラウザや環境によっては利用できないため、注意が必要です:

  • Safari はベータ版 16.4 でサポート
  • Firefox は非サポート
  • Node.js は「生の deflate 形式」(deflate-raw) は非サポート

参考「Compression Streams API - Web API | MDN

1.3. ライブラリ pako について

ライブラリ pako は deflate アルゴリズムでデータの圧縮や解凍を行う機能を提供します。

ライブラリ pako 自体は MIT License で利用できます。

(※ pako のビルド前のソースコードの一部は zlib License なため、ビルド前のソースコードを利用する場合は注意。)

参考「GitHub - nodeca/pako: high speed zlib port to javascript, works in browser & node.js
参考「pako 2.1.0 API documentation

2. Deflate 圧縮および解凍

2.1. 生の Deflate 形式

Compression Streams API を用いる場合はデータをストリームでやり取りするため、データとストリームの相互変換を行う必要があります。

ReadableStream とデータの相互変換については別記事にしました。

参考「[JavaScript] ReadableStream とその他の型の相互変換 - Qiita

ReadableStream.prototype.pipeThrough メソッドを使用することでデータを変換するストリームを得られます。

CompressionStreamDecompressionStreamtransformStream として指定することでデータを圧縮および解凍するストリームを取得できます (TransformStream を継承していなくても指定可能です) 。

参考「ReadableStream.pipeThrough() - Web API | MDN
参考「CompressionStream - Web API | MDN
参考「DecompressionStream - Web API | MDN

Compression Streams API で生の Deflate 形式を扱うには formatdeflate-raw を指定します。

参考「CompressionStream() - Web API | MDN
参考「DecompressionStream() - Web API | MDN

Compression Streams API を用いる場合
const deflateRaw = async data => {
	const readableStream = new Blob([data]).stream();
	const compressedStream = readableStream.pipeThrough(
        // メモ: 毎回インスタンス化する必要がある
        new CompressionStream('deflate-raw'),
    );
	const arrayBuffer = await new Response(compressedStream).arrayBuffer();
	return arrayBuffer;
};

const inflateRaw = async data => {
	const readableStream = new Blob([data]).stream();
	const decompressedStream = readableStream.pipeThrough(
        // メモ: 毎回インスタンス化する必要がある
        new DecompressionStream('deflate-raw'),
    );
	const arrayBuffer = await new Response(decompressedStream).arrayBuffer();
	return arrayBuffer;
};

ライブラリ pako を用いる場合、pako.deflateRaw 関数及び pako.inflateRaw 関数を使用することで生の Deflate 形式で圧縮および解凍できます。

参考「deflateRaw - pako 2.1.0 API documentation
参考「inflateRaw - pako 2.1.0 API documentation

ライブラリ pako を用いる場合
const deflateRaw = data => pako.deflateRaw(data).buffer;

const inflateRaw = data => pako.inflateRaw(data).buffer;

上記の方法で定義した deflateRaw 関数および inflateRaw 関数で以下のようにして圧縮および解凍できます。

const textA = '犬や猫は肉球を持つが、兎は肉球を持たない。犬と猫は食肉目だが、兎は重歯目である。';
console.log(textA);
const textUint8ArrayA = new TextEncoder().encode(textA);
console.log(textUint8ArrayA.length);

// 圧縮
// メモ: deflateRaw 関数に直接 textA を渡すことも可
const arrayBufferA = await deflateRaw(textUint8ArrayA);
console.log(arrayBufferA.byteLength);

// 解凍
// TODO: 実際は例外処理が必要
const arrayBufferB = await inflateRaw(arrayBufferA);
console.log(arrayBufferB.byteLength);
const textB = new TextDecoder().decode(arrayBufferB);
console.log(textB);
実行結果
犬や猫は肉球を持つが、兎は肉球を持たない。犬と猫は食肉目だが、兎は重歯目である。
120
84
120
犬や猫は肉球を持つが、兎は肉球を持たない。犬と猫は食肉目だが、兎は重歯目である。

2.2. zlib 形式

同様にして zlib 形式で圧縮および解凍することができます。

Compression Streams API で zlib 形式を扱うには formatdeflate を指定します。

参考「CompressionStream() - Web API | MDN
参考「DecompressionStream() - Web API | MDN

Compression Streams API を用いる場合
const deflate = async data => {
	const readableStream = new Blob([data]).stream();
	const compressedStream = readableStream.pipeThrough(
        // メモ: 毎回インスタンス化する必要がある
        new CompressionStream('deflate'),
    );
	const arrayBuffer = await new Response(compressedStream).arrayBuffer();
	return arrayBuffer;
};

const inflate = async data => {
	const readableStream = new Blob([data]).stream();
	const decompressedStream = readableStream.pipeThrough(
        // メモ: 毎回インスタンス化する必要がある
        new DecompressionStream('deflate'),
    );
	const arrayBuffer = await new Response(decompressedStream).arrayBuffer();
	return arrayBuffer;
};

ライブラリ pako を用いる場合、pako.deflate 関数及び pako.inflate 関数を使用することで zlib 形式で圧縮および解凍できます。

参考「deflate - pako 2.1.0 API documentation
参考「inflate - pako 2.1.0 API documentation

ライブラリ pako を用いる場合
const deflate = data => pako.deflate(data).buffer;

const inflate = data => pako.inflate(data).buffer;

上記の方法で定義した deflate 関数および inflate 関数で以下のようにして圧縮および解凍できます。

const textA = '犬や猫は肉球を持つが、兎は肉球を持たない。犬と猫は食肉目だが、兎は重歯目である。';
console.log(textA);
const textUint8ArrayA = new TextEncoder().encode(textA);
console.log(textUint8ArrayA.length);

// 圧縮
// メモ: deflate 関数に直接 textA を渡すことも可
const arrayBufferA = await deflate(textUint8ArrayA);
console.log(arrayBufferA.byteLength);

// 解凍
// TODO: 実際は例外処理が必要
const arrayBufferB = await inflate(arrayBufferA);
console.log(arrayBufferB.byteLength);
const textB = new TextDecoder().decode(arrayBufferB);
console.log(textB);
実行結果
犬や猫は肉球を持つが、兎は肉球を持たない。犬と猫は食肉目だが、兎は重歯目である。
120
90
120
犬や猫は肉球を持つが、兎は肉球を持たない。犬と猫は食肉目だが、兎は重歯目である。

zlib 形式は生の deflate 形式のデータにヘッダとフッタを付け加えるため、生の deflate 形式より圧縮後のサイズが大きくなります (6 バイトまたは 10 バイト) 。

参考「Page 4 - RFC 1950: ZLIB Compressed Data Format Specification version 3.3」(「2.2. Data format」を参照)

2.3. gzip 形式

同様にして gzip 形式で圧縮および解凍することができます。

Compression Streams API で gzip 形式を扱うには formatgzip を指定します。

参考「CompressionStream() - Web API | MDN
参考「DecompressionStream() - Web API | MDN

Compression Streams API を用いる場合
const gzip = async data => {
	const readableStream = new Blob([data]).stream();
	const compressedStream = readableStream.pipeThrough(
        // メモ: 毎回インスタンス化する必要がある
        new CompressionStream('gzip'),
    );
	const arrayBuffer = await new Response(compressedStream).arrayBuffer();
	return arrayBuffer;
};

const ungzip = async data => {
	const readableStream = new Blob([data]).stream();
	const decompressedStream = readableStream.pipeThrough(
        // メモ: 毎回インスタンス化する必要がある
        new DecompressionStream('gzip'),
    );
	const arrayBuffer = await new Response(decompressedStream).arrayBuffer();
	return arrayBuffer;
};

ライブラリ pako を用いる場合、pako.gzip 関数及び pako.ungzip 関数を使用することで gzip 形式で圧縮および解凍できます。

参考「gzip - pako 2.1.0 API documentation
参考「ungzip - pako 2.1.0 API documentation

ライブラリ pako を用いる場合
const gzip = data => pako.gzip(data).buffer;

const ungzip = data => pako.ungzip(data).buffer;

上記の方法で定義した gzip 関数および ungzip 関数で以下のようにして圧縮および解凍できます。

const textA = '犬や猫は肉球を持つが、兎は肉球を持たない。犬と猫は食肉目だが、兎は重歯目である。';
console.log(textA);
const textUint8ArrayA = new TextEncoder().encode(textA);
console.log(textUint8ArrayA.length);

// 圧縮
// メモ: gzip 関数に直接 textA を渡すことも可
const arrayBufferA = await gzip(textUint8ArrayA);
console.log(arrayBufferA.byteLength);

// 解凍
// TODO: 実際は例外処理が必要
const arrayBufferB = await ungzip(arrayBufferA);
console.log(arrayBufferB.byteLength);
const textB = new TextDecoder().decode(arrayBufferB);
console.log(textB);
実行結果
犬や猫は肉球を持つが、兎は肉球を持たない。犬と猫は食肉目だが、兎は重歯目である。
120
102
120
犬や猫は肉球を持つが、兎は肉球を持たない。犬と猫は食肉目だが、兎は重歯目である。

gzip 形式は生の deflate 形式のデータにヘッダとフッタを付け加えるため、生の deflate 形式より圧縮後のサイズが大きくなります (18 バイト以上) 。

参考「Page 5 - RFC 1952: GZIP file format specification version 4.3」(「2.3. Member format」を参照)

6
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
5