{T}

Zlib 模块

概述

zlib 模块封装了 zlib C 库(由 Jean-loup Gailly 和 Mark Adler 开发),提供数据压缩与解压缩能力。Node.js 的 HTTP 请求/响应体压缩、文件压缩、日志归档等功能都依赖此模块。从 Node.js v16.7.0 起,zlib 还实现了 WHATWG 规范的 CompressionStream / DecompressionStream API,与浏览器保持一致。

图表渲染中…

压缩算法对比

算法格式标识压缩率速度浏览器支持用途
Deflatedeflate通用压缩
GzipgzipHTTP 压缩标准
DeflateRawdeflateRaw自定义协议
Brotlibr✅ (HTTPS)Web 静态资源压缩
ZstandardNode.js v22+
图表渲染中…

Deflate/Inflate 工作原理

LZ77 + Huffman 编码

Deflate 算法由两个阶段组成:

图表渲染中…

Gzip 格式结构

Gzip 在 Deflate 之上包装了文件头和校验尾:

code
┌────────────────────────────────────────────────────────┐
│ Gzip 格式                                              │
├──────────┬─────────────────────────────┬──────────────┤
│ Header   │ Compressed Data (Deflate)   │ Footer       │
│ 10 bytes │ 变长                         │ 8 bytes      │
├──────────┼─────────────────────────────┼──────────────┤
│ Magic:   │ LZ77 + Huffman 编码数据      │ CRC32: 4B    │
│ 1f 8b    │                              │ Size: 4B     │
│ CM: 08   │                              │ (原始大小)    │
│ FLG      │                              │              │
│ MTIME    │                              │              │
│ XFL      │                              │              │
│ OS       │                              │              │
└──────────┴─────────────────────────────┴──────────────┘

Node.js Zlib API

同步 API

javascript
const zlib = require('zlib');

// 同步压缩
const compressed = zlib.deflateSync('hello world');
const decompressed = zlib.inflateSync(compressed);
console.log(decompressed.toString());  // 'hello world'

// Gzip
const gzipped = zlib.gzipSync('hello world');
const ungzipped = zlib.gunzipSync(gzipped);

// Brotli
const brotlied = zlib.brotliCompressSync('hello world');
const unbrotlied = zlib.brotliDecompressSync(brotlied);

注意:同步 API 会阻塞事件循环,仅适用于小数据量或启动阶段。生产环境应使用流式 API。

流式 API(推荐)

javascript
const zlib = require('zlib');
const fs = require('fs');

// 文件压缩管道
fs.createReadStream('access.log')
  .pipe(zlib.createGzip())
  .pipe(fs.createWriteStream('access.log.gz'));

// 文件解压管道
fs.createReadStream('access.log.gz')
  .pipe(zlib.createGunzip())
  .pipe(fs.createWriteStream('access.log'));
图表渲染中…

异步回调 API

javascript
zlib.gzip('hello world', (err, buffer) => {
  if (err) throw err;
  console.log(buffer.length);  // 压缩后大小

  zlib.gunzip(buffer, (err, result) => {
    if (err) throw err;
    console.log(result.toString());  // 'hello world'
  });
});

// Promise 化
const { promisify } = require('util');
const gzipAsync = promisify(zlib.gzip);
const gunzipAsync = promisify(zlib.gunzip);

const compressed = await gzipAsync('hello world');
const decompressed = await gunzipAsync(compressed);

压缩选项详解

Deflate/Gzip 选项

javascript
zlib.createGzip({
  level: zlib.constants.Z_BEST_COMPRESSION,  // 压缩级别 0-9
  memLevel: 8,        // 内存级别 1-9(影响速度/内存)
  strategy: zlib.constants.Z_DEFAULT_STRATEGY,  // 压缩策略
  windowBits: 15,     // 窗口大小(历史缓冲区)
  chunkSize: 16 * 1024,  // 输出块大小
});
压缩级别常量压缩率速度适用场景
0Z_NO_COMPRESSION最快不压缩
1Z_BEST_SPEED实时数据
6Z_DEFAULT_COMPRESSION通用
9Z_BEST_COMPRESSION静态资源

Brotli 选项

javascript
zlib.createBrotliCompress({
  params: {
    [zlib.constants.BROTLI_PARAM_QUALITY]:
      zlib.constants.BROTLI_MAX_QUALITY,  // 质量 0-11
    [zlib.constants.BROTLI_PARAM_LGWIN]:
      22,  // 窗口大小 10-24
  },
});
质量级别压缩率速度适用场景
1实时压缩
4中低动态内容
6通用
11静态资源预压缩

HTTP 压缩实战

服务端压缩响应

javascript
const http = require('http');
const zlib = require('zlib');

const server = http.createServer((req, res) => {
  const acceptEncoding = req.headers['accept-encoding'] || '';
  const payload = JSON.stringify({ data: 'large response body...' });

  // 根据客户端支持选择压缩算法
  if (acceptEncoding.includes('br')) {
    res.writeHead(200, { 'Content-Encoding': 'br' });
    zlib.brotliCompress(payload, (_, result) => res.end(result));
  } else if (acceptEncoding.includes('gzip')) {
    res.writeHead(200, { 'Content-Encoding': 'gzip' });
    zlib.gzip(payload, (_, result) => res.end(result));
  } else if (acceptEncoding.includes('deflate')) {
    res.writeHead(200, { 'Content-Encoding': 'deflate' });
    zlib.deflate(payload, (_, result) => res.end(result));
  } else {
    res.writeHead(200);
    res.end(payload);
  }
});
图表渲染中…

客户端解压响应

javascript
const http = require('http');
const zlib = require('zlib');

const options = {
  hostname: 'example.com',
  headers: { 'Accept-Encoding': 'gzip, deflate, br' },
};

http.get(options, (res) => {
  const encoding = res.headers['content-encoding'];
  let decompressStream;

  switch (encoding) {
    case 'br':
      decompressStream = zlib.createBrotliDecompress();
      break;
    case 'gzip':
      decompressStream = zlib.createGunzip();
      break;
    case 'deflate':
      decompressStream = zlib.createInflate();
      break;
    default:
      decompressStream = res;
  }

  let data = '';
  decompressStream.on('data', (chunk) => { data += chunk; });
  decompressStream.on('end', () => { console.log(data); });

  if (decompressStream !== res) {
    res.pipe(decompressStream);
  }
});

CompressionStream — Web 标准 API

Node.js v16.7.0 实现了 WHATWG 规范的 CompressionStream / DecompressionStream

javascript
// CompressionStream(浏览器 + Node.js 通用)
const { CompressionStream } = require('stream/web');

const readable = new ReadableStream({
  start(controller) {
    controller.enqueue(new TextEncoder().encode('hello world'));
    controller.close();
  },
});

const compressed = readable.pipeThrough(new CompressionStream('gzip'));
const decompressed = compressed.pipeThrough(new DecompressionStream('gzip'));

Legacy vs CompressionStream

维度Legacy APICompressionStream
规范Node.js 私有WHATWG 标准
接口Node.js Transform StreamWHATWG TransformStream
算法deflate/gzip/deflateRaw/brotligzip/deflate/deflate-raw
浏览器兼容
流类型Node.js StreamWeb Stream

常见陷阱

1. 解压时 windowBits 不匹配

javascript
// ❌ 压缩和解压使用不同的 windowBits
zlib.deflateSync(data, { windowBits: 15 });
zlib.inflateSync(compressed, { windowBits: 8 });  // 可能失败

// ✅ 保持一致,或使用默认值
zlib.deflateSync(data);
zlib.inflateSync(compressed);

2. 小数据压缩后反而更大

javascript
const small = 'hi';
zlib.gzipSync(small).length;  // ~26 bytes — 比原始数据更大

Gzip 头部 10 字节 + 尾部 8 字节 = 18 字节固定开销。对小于 ~150 字节的数据,压缩通常没有收益。

3. Brotli 仅在 HTTPS 下生效

浏览器只在 HTTPS 连接中发送 Accept-Encoding: br,HTTP 连接不会使用 Brotli。

4. 流式解压的 truncated 数据

javascript
// ❌ 不完整的压缩数据会导致解压流报错
const stream = zlib.createGunzip();
stream.on('error', (err) => {
  if (err.code === 'Z_DATA_ERROR') {
    console.error('数据损坏或不完整');
  }
});