@cjser/lzma1

中文文档
0.6.3-cjser.2发布于 2026/11/01Apache-2.0TypeScriptESM + CJS零依赖

一个JavaScript实现的Lempel-Ziv-Markov (LZMA)链压缩算法

中文使用文档
文档目录20 节
  1. 为什么
  2. 功能
  3. 安装
  4. npm
  5. 浏览器
  6. 快速启动
  7. 压缩字符串
  8. 处理二进制数据
  9. 压缩选项
  10. Raw LZMA1和LZMA2
  11. 流
  12. 错误
  13. HTML例子
  14. 它是如何工作的
  15. LZMA头
  16. LZMA2
  17. 基准
  18. 验证
  19. 相关
  20. cjser

lzma1

Lempel-Ziv-Markov链算法(LZMA)的TypeScript实现 为 .lzma 格式,从 XZ代表Java. 它也会解码 7z存档中的原始LZMA1和LZMA2数据 .xz 文件。它开始 作为一个 叉 的 Nathan拉格的 LZMA-JS包。

为什么

JavaScript中有许多LZMA实现,但大多数都过时了, 没有维护,缺乏类型支持,或者依赖于特定的运行时api(例如, node . js Buffer)。

这个版本是XZ的Java移植版本,是LZMA的一个简洁实现 Collin和Igor Pavlov,通过LZMA SDK进行了一些优化。它是 兼容 xz --format=lzma, 7-Zip和LZMA SDK,并且只使用web 标准的api。

功能

  • 编码和解码 .lzma 数据,可与 xz、7-Zip和LZMA SDK
  • 将原始LZMA1和LZMA2数据解码为7z和 .xz 存储它,给定 容器中的属性
  • 字符串(UTF-8)和二进制数据(ArrayBuffer 或任何类型数组)
  • 异步压缩和解压缩与等待进度回调; 用于生产、进度和取消
  • 压缩级别1-9或细粒度选项(字典大小, lc/lp/pb,……)
  • 网页流(TransformStream)用于压缩和解压缩流
  • 截断输入和大部分损坏输入的错误
  • 不可信原始数据的内存限制,在KiB中,如Java的XZ
  • 一个可选的输出大小限制,以防御“解压炸弹”攻击
  • 没有依赖,运行在浏览器、worker、Node.js、Deno和Bun中

安装

[!请注意) 这个包是本地的 ESM 并且不再提供CommonJS 出口。如果你的项目使用CommonJS,你将不得不转换为ESM或使用 动态 import() 函数。

npm

npm install lzma1

浏览器

<script type="module">
    import {
        compress,
        compressString,
        decompress,
        decompressString,
    } from "https://esm.sh/lzma1@latest";
</script>

快速启动

六个功能压缩和解压 .lzma 格式,三解码 原始数据:

// For binary data
compress(data: ArrayBuffer | ArrayBufferView, mode?: 1-9 | CompressionOptions): Uint8Array
compressAsync(data: ArrayBuffer | ArrayBufferView, mode?: 1-9 | CompressAsyncOptions): Promise<Uint8Array>
decompress(data: ArrayBuffer | ArrayBufferView, options?: DecompressionOptions): Uint8Array
decompressAsync(data: ArrayBuffer | ArrayBufferView, options?: DecompressAsyncOptions): Promise<Uint8Array>

// For string data
compressString(data: string, mode?: 1-9 | CompressionOptions): Uint8Array
decompressString(data: ArrayBuffer | ArrayBufferView, options?: DecompressionOptions): string

// For raw data without a .lzma header, e.g. from 7z or .xz
decompressRawLzma1(data: ArrayBuffer | ArrayBufferView, options: RawLzma1Options): Uint8Array
decompressRawLzma1Async(data: ArrayBuffer | ArrayBufferView, options: RawLzma1AsyncOptions): Promise<Uint8Array>
decompressRawLzma2(data: ArrayBuffer | ArrayBufferView, options: RawLzma2Options): Uint8Array

这个 Async 函数返回的字节数与不带 后缀,并可选地等待 onProgressAsync 在它们工作的时候回调 调用者可以屈服于事件循环,显示进度或取消。这个 Callback的文档注释中有一个例子。重要:呼叫者可以阅读 data 但不得修改它,直到返回的承诺已经解决。

字符串被编码为UTF-8。 decompressString 也解码数据压缩 它使用Java的“修改过的UTF-8”。这个 LZMA 类 再次从v0.2.0导出;它的 decompress 现在返回一个 Uint8Array.

压缩字符串

import {
    compressString,
    decompressString,
} from "lzma1";

const data = "Hello World!";
const compressed = compressString(data, 1); // level 1 (fastest)
const decompressed = decompressString(compressed);

// data === decompressed

处理二进制数据

import {
    compress,
    decompress,
} from "lzma1";

const binaryData = new Uint8Array([0x01, 0x02, 0x03, 0x04]);
const compressed = compress(binaryData, 5); // default level
const decompressed = decompress(compressed);

压缩选项

级别(1-9,默认为5)交易速度大小:

水平编码器匹配仪字典
1快hc41 MiB
2快hc42 MiB
3快hc44 MiB
4正常的bt41 MiB
5正常的bt42 MiB
6正常的bt44 MiB
7正常的bt48 MiB
8正常的bt416 MiB
9正常的bt432 MiB

传递选项而不是关卡。没有给出的选项来自关卡。

const compressed = compress(data, {
    level: 9, // preset 1-9
    dictSize: 1 << 20, // dictionary size in bytes, 4 KiB - 768 MiB
    lc: 3, // literal context bits, 0-4
    lp: 0, // literal position bits, 0-4, lc + lp <= 4
    pb: 2, // position bits, 0-4
    mode: "normal", // "fast" or "normal" (optimal parsing)
    niceLen: 64, // match length that is good enough, 8-273
    matchFinder: "bt4", // "hc4" (hash chain) or "bt4" (binary tree)
    depth: 0, // match finder search depth, 0 = automatic
    endMarker: true, // false omits the end marker, see below
});

一个大于输入的字典被缩减到适合它,这节省了内存 压缩和解压缩时。数据与 lc + lp > 4 从其他 编码器(例如7-Zip)仍然可以解压缩。

报头保存未压缩的大小,因此数据后面的结束标记是 对于读取它的解码器来说是冗余的。 endMarker: false 省略它,比如XZ Java和7-Zip没有 -eos,并节省5-6字节,这对于小的很重要 有效载荷,比如QR码。解码器不知道大小需要标记 查找结尾:原始LZMA解码器(xz --format=raw, Python的 FORMAT_RAW) 报告一个错误或解码到没有它的结束。只有当数据 用它的头(这个库, xz --format=lzma(7-Zip)或 大小在其他地方是已知的。知道大小的严格解码器可能会拒绝a 标记,例如XZ表示Java的raw LZMAInputStream 构造函数没有 enableRelaxedEndCondition().

Raw LZMA1和LZMA2

7z档案和 .xz 文件存储LZMA数据,没有 .lzma Header和keep 它的属性在它们自己的头文件中。将这些属性传递给 decompressRawLzma1 或者 decompressRawLzma2. 这个库不解析7z 或者 .xz 容器。

import {
    decompressRawLzma1,
    decompressRawLzma2,
} from "lzma1";

// A 7z LZMA entry. Its 5-byte coder properties are the byte
// (pb * 5 + lp) * 9 + lc and the dictionary size, 32-bit little-endian.
const file = decompressRawLzma1(packed, {
    lc: 3, // literal context bits, 0-8
    lp: 0, // literal position bits, 0-4
    pb: 2, // position bits, 0-4
    dictSize: 1 << 24, // dictionary size in bytes
    uncompressedSize: 123_456, // optional, the size from the 7z header
    memoryLimitKb: 65_536, // optional
});

// LZMA2 data from a 7z entry or an .xz block
const block = decompressRawLzma2(packed, {
    dictSize: 1 << 20, // from the LZMA2 properties byte, see "LZMA2" below
    memoryLimitKb: 65_536, // optional
});

LZMA2标记自己的末端,不需要大小。Raw LZMA1需要未压缩的 大小或结束标记。7-Zip写无止境 默认情况下,标记,所以从7z头传递大小。 xz --format=raw --lzma1 总是写一个。对于已知的大小,数据必须 在那里结束,有或没有结束标记,如with decompress. 结束标记 在此之前,大小是一个错误,如 strict.

字典大小来自容器,以及损坏或恶意的头文件 最高可申领4gib。 memoryLimitKb 就像Java的XZ一样 memoryLimit. 解码器根据字典大小估算其在KiB中的内存 lc 和 lp 对于LZMA1,并抛出 MemoryLimitError 在它分配任何东西之前 估价超出了限额。错误的 memoryNeededKb 和 memoryLimitKb 字段持有 两个数字。估计值按给定的大小计算字典。这个 字典缓冲区最多从64kib开始,只有在输出到达时才翻倍, 因此,在输出增加之前,一个巨大的声称字典不需要任何成本。已知的LZMA1 大小改变了这一点: decompressRawLzma1 然后一次分配最多64个MiB 对于字典或输出,它为 decompress 尺寸在吗 这个 .lzma 头。估计不包括输出。

流

Compress 和 Decompress 是 TransformStreamS,像内置的一样使用 CompressionStream:

import {
    Compress,
    Decompress,
} from "lzma1";

// Compress a file
const compressed = file.stream().pipeThrough(new Compress(5));

// Decompress a download
const response = await fetch("/data.lzma");
const text = await new Response(response.body.pipeThrough(new Decompress())).text();

流的大小是事先不知道的,所以标头将其标记为未知和 数据总是以结束标记结束: new Compress({ endMarker: false }) 抛出一个 RangeError.

DecompressRawLzma1 和 DecompressRawLzma2 解码流中的原始数据和 采用与函数相同的选项。无效选项和字典结束 从构造函数抛出内存限制。

const output = response.body.pipeThrough(new DecompressRawLzma2({ dictSize: 1 << 20 }));

在TypeScript中,它们的类型需要 TransformStream 从 DOM 自由或 @types/node (或 skipLibCheck)。

错误

解压缩被截断或损坏的数据会抛出 Error,如。 "Truncated input" 或者 "Corrupted input: match distance is too large". 这个 .lzma 格式没有校验和,所以一些损坏的数据解码没有 输出错误。原始LZMA1和LZMA2数据也没有;7 z 和 .xz 将校验和保存在容器中。 无效选项引发 RangeError,还有一本字典 memoryLimitKb 抛出一个 MemoryLimitError.

数据必须以标题中的大小结束,带或不带结束标记 在它之后。在该大小之前的结束标记也结束了数据,因为有些 制作人写的大小太大了。 strict 拒绝它,如XZ表示Java xz做:

decompress(data, { strict: true });
new Decompress({ strict: true });

在解压缩来自不可信源的数据时, maxOutputLength 可以保护 对抗“解压炸弹”攻击,在这种攻击中,几个字节的精心制作的数据会扩展 g。对于除原始LZMA2之外的每个解码器,它以字节为单位限制输出。 与 strict对于原始LZMA1,较大的声明大小会抛出错误 OutputLimitError 在分配任何东西之前。否则,与未知一样 大小,解码抛出一个一旦输出超过限制,因为一个结束标记 仍然可以结束其中的数据:

decompress(data, { maxOutputLength: 1_000_000 });

HTML例子

<!DOCTYPE html>
<html>
    <body>
        <textarea id="input">Hello World!</textarea><br />
        <button id="run">Compress & Decompress</button>
        <div id="result"></div>

        <script type="module">
            import { compressString, decompressString } from "https://esm.sh/lzma1@latest";

            document.getElementById("run").onclick = () => {
                const text = document.getElementById("input").value;
                const compressed = compressString(text);
                const decompressed = decompressString(compressed);

                document.getElementById("result").innerHTML =
                    `Original: ${text.length} bytes<br>` +
                    `Compressed: ${compressed.length} bytes<br>` +
                    `Result: ${decompressed}`;
            };
        </script>
    </body>
</html>

它是如何工作的

LZMA (Lempel-Ziv-Markov chain Algorithm)是一种压缩算法 将字典(lz77风格)压缩器与范围编码器相结合。它是已知的 因为它的高压缩比和在7z和xz格式中使用。

LZMA头

这个 .lzma 数据以一个13字节的头开始,后面跟着压缩数据:

packet-beta
  0-7: "Properties (lc, lp, pb)"
  8-39: "Dictionary Size (32-bit, LE)"
  40-103: "Uncompressed Size (64-bit, LE)"

第一个字节是 (pb * 5 + lp) * 9 + lc. 所有的未压缩大小 0xFF 字节表示未知。更多的 信息 关于LZMA报头 结构。

LZMA2

LZMA2将数据分成未压缩的2 MiB和64 KiB的块 压缩。每个块都以一个控制字节开始:

控制块
0 x00数据结束
0 x01未压缩,重置字典
0 x02未压缩的
0 x80-0x9fLZMA
0 xa0-0xbfLZMA,重置状态
0 xc0-0xdfLZMA,重置状态,new lc/lp/pb
0 xe0-0xffLZMA,重置字典和状态,new lc/lp/pb

在接下来的2个字节中,未压缩的块的大小为- 1。一个LZMA Chunk有这样的头,大小为- 1,大端序:

packet-beta
  0-7: "Control (low 5 bits: size bits 16-20)"
  8-23: "Uncompressed size, bits 0-15"
  24-39: "Compressed size"
  40-47: "Properties (0xC0 and up)"

第一个块必须重置字典,并且 lc + lp 不能超过4。 7 z和 .xz 将字典大小存储为一个字节 n. 尺寸是 0xFFFFFFFF 用于 n = 40 和 (2 | (n & 1)) * 2 ** ((n >> 1) + 11) 下面 从4千磅起。

基准

看到 文档/ benchmarks.md, 板凳上 来运行它们。

验证

编码器的输出字节与 XZ代表Java 在提交 6925f81 输入和编码器设置的语料库。解码器处理一组 除了两种已记录的腐化类型之外,腐化流的方式相同 它接受的输入,而Java的XZ拒绝它们。原始的LZMA1和LZMA2 解码器处理它们自己的一组流,就像Java的XZ一样。CI检查 所有三个。 设备/ README.md 描述语料库以及如何 重新生成它。

对于这个代码来说,通过测试是不够的。不同于的编码器决策 Java的XZ通常仍然产生正确解压缩的有效流, 因此,往返测试忽略了它,代码虽然很小,但具有巨大的输入 空间中,许多边缘情况和模算法可以将这些差异隐藏在其中。 所以对编码员的每一次更改都有一个书面的参数 文档/ verification.md要么这种行为没有改变 对于所有输入,或者对Java代码的文档化偏离。身份测试 在语料库中检查这些参数。

自己检查: bun test src/xz-java-identity_test.ts,或者 node fixtures/verify.ts (节点>= 22.18)在V8上运行它们。

cjser

这个包是由cjser为仍然需要的项目生成的commonjs兼容的构建 require() 支持。源版本与原始的npm包版本匹配,对这个生成的构建带有cjser预发布后缀。 原始存储库: https://github.com/xseman/lzma1

版本与发布记录

@cjser/lzma1 共发布 6 个版本,首次发布于 2026/05/07;过去 12 个月发布了 6 个版本。

版本发布日期距今
0.6.3-cjser.2latest2026/11/012 天前
0.6.2-cjser.22026/10/313 天前
0.6.1-cjser.22026/10/1816 天前
0.6.0-cjser.22026/10/1420 天前
0.4.0-cjser.22026/10/021 个月前
0.3.0-cjser.22026/05/075 个月前

关于 @cjser/lzma1 的常见问题

@cjser/lzma1 是什么?

一个JavaScript实现的Lempel-Ziv-Markov (LZMA)链压缩算法

如何安装 @cjser/lzma1?

在项目目录运行 npm install @cjser/lzma1,或使用 pnpm add / yarn add / bun add。

@cjser/lzma1 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@cjser/lzma1 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@cjser/lzma1 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=18.0.0。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@cjser/lzma1 可以商用吗?

@cjser/lzma1 使用 Apache-2.0 许可。宽松许可:允许商业使用、修改和再分发,并附带明确的专利授权;分发时需保留许可与 NOTICE 文件,修改过的文件要注明改动。以上为概括,不构成法律意见,请以许可原文为准。

@cjser/lzma1 还在维护吗?

最新版本 0.6.3-cjser.2 发布于 2026/11/01(2 天前);至今共发布 6 个版本,其中过去 12 个月发布了 6 个;首次发布于 2026/05/07。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。