文章目录
- 前言
- 一、数据包
- 二、传输距离
- [三 解码过程](#三 解码过程)
-
-
- [范围编码比特(Range coding of bits)](#范围编码比特(Range coding of bits))
- 归一化(Normalization)
- 上下文基础的范围解码比特
- 固定概率范围解码比特
- 注意事项
-
- 解码树
- 压缩参数
-
-
- lclppb属性字节
- 配置限制
- [7-zip LZMA文件格式中的配置](#7-zip LZMA文件格式中的配置)
- LZMA2流中的配置
-
- 压缩过程
前言
LZMA算法,全称为Lempel-Ziv-Markov chain Algorithm,是一种用于无损耗数据压缩的算法。它由Igor Pavlov在1996年或1998年开发,并首次应用于7-Zip压缩软件的7z格式中。LZMA算法使用字典压缩方案,与1977年由Abraham Lempel和Jacob Ziv发表的LZ77算法相似,但LZMA在比特级别而非字节级别上应用修改后的LZ77算法。
LZMA算法的主要特点包括:
- 高压缩比:LZMA通常能够比bzip2、DEFLATE等其他算法获得更好的压缩效果。
- 可变字典大小:LZMA支持的压缩字典大小可变,最大可达4GB。
- 快速解压缩:尽管压缩比较高,但LZMA保持了解压缩速度与其他常用压缩算法相似。
LZMA2算法是LZMA的改进版,于2009年通过7-Zip软件更新引入。LZMA2的主要特点包括:
- 多线程压缩和解压缩:LZMA2支持可伸缩的多线程压缩和解压缩,提高了处理大数据时的效率。
- 高效压缩部分不可压缩数据:LZMA2能够更有效地压缩那些部分不可压缩的数据。
- 包含不同LZMA编码参数:LZMA2是一个容器格式,可以包含未压缩数据和LZMA压缩数据,可能包含多个不同的LZMA编码参数。
然而,也有观点认为LZMA2不如LZMA安全和高效。尽管如此,LZMA和LZMA2因其优秀的压缩性能和较快的解压缩速度,被广泛应用于各种压缩软件和文件格式中。
LZMA算法的核心是字典压缩技术,它是LZ77算法的一个变种,具有巨大的字典大小和对重复使用匹配距离的特殊支持。LZMA算法的输出随后通过范围编码器(range encoder)进行编码,使用复杂的模型对每个比特进行概率预测。
在LZMA之前,大多数编码模型都是基于字节的(即它们仅使用一系列上下文来表示同一字节中前一个比特的依赖关系)。LZMA的主要创新在于,它不使用通用的基于字节的模型,而是使用特定于每个字面量或短语表示中的位字段的上下文:这几乎和通用的基于字节的模型一样简单,但提供了更好的压缩效果,因为它避免了将不相关的比特混合在同一上下文中。
与传统的字典压缩(如zip和gzip格式中使用的)相比,LZMA的字典大小可以而且通常要大得多,利用现代系统上可用的大量内存。
LZMA的字典压缩器使用复杂的字典数据结构来查找匹配项,并产生一个字面量符号和短语引用的流,这个流由范围编码器逐位编码:可能有许多编码方式,使用动态规划算法在某些近似条件下选择一个最优的编码。
总结来说,LZMA算法通过其字典压缩技术和范围编码器,以及对每个比特的概率预测模型,实现了高效的数据压缩。这种算法特别适用于现代系统,因为它能够利用大量可用的内存来实现更大的字典大小,从而获得更高的压缩比。
一、数据包
LZMA压缩格式的概述如下:
-
压缩流:在LZMA压缩中,压缩后的流是一个比特流,使用自适应二进制范围编码器进行编码。这个流被划分为多个数据包,每个数据包描述单个字节或者一个LZ77序列,其中序列的长度和距离是隐式或显式编码的[8]。
-
数据包类型:LZMA压缩流中有7种类型的数据包,每种类型对应不同的压缩信息:
- LIT(0 + byteCode):使用自适应二进制范围编码器编码的单个字节。
- MATCH(1+0 + len + dist):典型的LZ77序列,描述序列长度和距离。
- SHORTREP(1+1+0+0):一个字节的LZ77序列,距离等于最后使用的LZ77距离。
- LONGREP[0](1+1+0+1 + len):LZ77序列,距离等于倒数第二次使用的LZ77距离。
- LONGREP[1](1+1+1+0 + len):LZ77序列,距离等于倒数第三次使用的LZ77距离。
- LONGREP[2](1+1+1+1+0 + len):LZ77序列,距离等于倒数第四次使用的LZ77距离。
- LONGREP[3](1+1+1+1+1 + len):LZ77序列,距离等于最近四次使用的LZ77距离之一。
其中,LONGREP[*]指的是LONGREP[0-3]数据包,*REP指的是LONGREP和SHORTREP,MATCH指的是MATCH和REP[8]。
-
上下文建模:每个数据包的每个部分都使用独立的上下文进行建模,因此每个比特的概率预测与同一类型的前一个数据包中的该比特(以及同一字段中相关的比特)的值相关联[7]。
-
距离列表处理:LONGREP[n]数据包在列表中移除已使用的距离,并重新将其插入到列表前端,以避免无用的重复条目,而MATCH数据包即使距离已存在于列表中,也仅将距离添加到前端。SHORTREP和LONGREP[0]则不改变列表[8]。
想象一下,你有一堆乐高积木,你想要把这些积木打包起来,以便它们占用的空间尽可能小。LZMA压缩就像是一个智能的打包机器,它用一种特别的方法来打包这些积木。
-
压缩流:这个智能打包机器会先把积木(数据)变成一长串的珠子(比特流),这些珠子是按照一种特殊的规则串起来的。
-
数据包:这串珠子被分成一小段一小段的,每一段都代表一些积木。这些小段我们叫做"数据包"。数据包有两种类型:
- 一种是"LIT",就是单个的积木块。
- 另一种是"MATCH"或者"REP",就是一串积木,这串积木和之前打包好的某一串非常相似。
-
数据包类型:数据包有7种不同的类型,每种类型都对应不同的打包方式:
- LIT:就是单个的积木块。
- MATCH:是一串积木,这串积木和之前某一串非常相似,但是需要指明这串积木的长度和它与之前那串相似积木的距离。
- SHORTREP 和 LONGREP:这些是特殊的MATCH,它们表示这串积木和最近几次打包的积木非常相似,不需要每次都指明距离,因为它们和最近几次使用的积木距离相似。
-
上下文建模:这个智能打包机器非常聪明,它会记住之前是怎么打包的,然后根据这些记忆来决定现在怎么打包。这样,每次打包的时候,它都能做出更好的决策。
-
距离列表处理:这个智能打包机器还有一个特别的地方,它会记录下最近几次使用的积木距离,这样在下次打包相似的积木时,就不需要每次都从头开始找,而是可以直接使用这些记录,这样打包起来就更快更高效。
LZMA压缩就像是有一个智能的打包机器,它通过记住之前怎么打包的,然后用一种特别的方法把积木(数据)打包成一串串珠子(比特流),这样就能占用更少的空间,而且下次再打开(解压缩)的时候,也能快速地恢复成原来的积木(数据)。
二、传输距离
好的,我来详细解释一下LZMA压缩中长度和距离的编码方式,并翻译成中文。
长度编码
在LZMA压缩中,长度的编码遵循以下规则:
-
长度码(比特序列):描述
- 0 + 3位:使用3位编码长度,范围从2到9。
- 1+0 + 3位:使用3位编码长度,范围从10到17。
- 1+1 + 8位:使用8位编码长度,范围从18到273。
这种编码方式允许我们用较少的比特来表示较短的长度,而较长的长度则需要更多的比特。例如,如果一个匹配的长度是5,那么只需要一个"0"加上3位来表示(因为5在2到9的范围内),总共只需要4位。如果长度是150,那么需要一个"1"、一个"1"和8位来表示,总共需要10位。
距离编码
在LZMA压缩中,距离的编码稍微复杂一些,它从6位的"距离槽"开始,这个距离槽决定了需要多少额外的比特来表示距离。距离是32位的,距离0指向字典中最近添加的字节。
-
6位距离槽:最高2位、固定0.5概率比特、上下文编码比特
- 0:00 0 0
- 1:01 0 0
- 2:10 0 0
- 3:11 0 0
- 4:10 0 1
- 5:11 0 1
- 6:10 0 2
- 7:11 0 2
- 8:10 0 3
- 9:11 0 3
- 10:10 0 4
- 11:11 0 4
- 12:10 0 5
- 13:11 0 5
- 14-62(偶数):10 ((slot / 2) - 5) 4
- 15-63(奇数):11 ((slot - 1) / 2 - 5) 4
这个表格告诉我们,对于不同的距离槽值,需要多少额外的比特来表示距离。例如,如果距离槽是14,那么需要4位额外的比特来表示距离,因为14是偶数,所以我们使用"10"加上(slot / 2) - 5位额外的比特,总共需要6位。
好的,我来用更通俗的语言解释一下LZMA压缩中长度和距离的编码方式。
想象你在玩一个游戏,游戏里有一个任务是传递秘密信息。这个信息是由一系列的数字组成的,这些数字代表了一些特定的动作或者指令。但是,你不能直接传递这些数字,因为这样太容易被发现了。所以,你决定用一种特殊的编码方式来传递这些数字。
- 短长度:如果这个数字很小(比如2到9),你只需要用一个简单的信号(比如一个手势或者一个眼神)来表示这个数字。这个信号很短,只需要3个动作。
- 中等长度:如果这个数字稍微大一些(比如10到17),你就需要用一个稍微复杂一点的信号(比如两个手势)来表示这个数字。这个信号需要4个动作。
- 长长度:如果这个数字很大(比如18到273),你就需要用一个更复杂的信号(比如三个手势加上一些特定的动作)来表示这个数字。这个信号需要9个动作。
现在,假设在这个游戏里面,你不仅要传递数字,还要传递这些数字之间的距离。这个距离告诉你,你需要在哪些数字之间做动作。
- 距离槽:你有一个特殊的工具,叫做"距离槽"。这个工具可以帮助你确定需要多少额外的信息来准确传递距离。这个工具有6个等级,每个等级对应不同的距离。
- 简单距离:如果距离很近(0到3),你不需要额外的信息,直接用一个简单的信号就可以表示。
- 中等距离:如果距离稍微远一些,你需要一些额外的信息。比如,如果距离槽是14,你需要4个额外的动作来表示这个距离。
- 复杂距离:如果距离很远,你需要更多的额外信息。这个信息是通过一些固定的信号和一些根据上下文变化的信号来传递的。
三 解码过程
范围编码比特(Range coding of bits)
LZMA数据在最低级别上是由范围解码器逐比特解码的,这个过程是由LZMA解码器指导的。
-
上下文基础的范围解码 :LZMA算法通过传递一个"上下文"给范围解码器来调用上下文基础的范围解码。这个上下文包括一个无符号的11位变量
prob
(通常使用16位数据类型实现),它代表比特为0的预测概率,这个值由范围解码器读取和更新(并且应该初始化为2^10,代表0.5的概率)。 -
固定概率范围解码:与上下文基础的范围解码不同,固定概率范围解码假设概率为0.5,但操作略有不同。
范围解码器的状态由两个无符号的32位变量组成:range
(代表范围大小)和code
(代表范围内的编码点)。
初始化范围解码器包括将range
设置为2^32 - 1,将code
设置为从流的第二个字节开始解释为大端序的32位值;流的第一个字节被完全忽略。
归一化(Normalization)
归一化的步骤如下:
- 将
range
和code
都左移8位。 - 从压缩流中读取一个字节。
- 将
code
的最低8位设置为读取的字节值。
上下文基础的范围解码比特
使用prob
概率变量进行上下文基础的范围解码比特的步骤如下:
- 如果
range
小于2^24,执行归一化。 - 设置
bound
为floor(range / 2^11) * prob
。 - 如果
code
小于bound
:- 设置
range
为bound
。 - 设置
prob
为prob + floor((2^11 - prob) / 2^5)
。 - 返回比特0。
- 设置
- 否则(如果
code
大于或等于bound
):- 设置
range
为range - bound
。 - 设置
code
为code - bound
。 - 设置
prob
为prob - floor(prob / 2^5)
。 - 返回比特1。
- 设置
固定概率范围解码比特
固定概率范围解码比特的步骤如下:
- 如果
range
小于2^24,执行归一化。 - 设置
range
为floor(range / 2)
。 - 如果
code
小于range
:- 返回比特0。
- 否则(如果
code
大于或等于range
):- 设置
code
为code - range
。 - 返回比特1。
- 设置
在Linux内核实现中,为了性能原因,固定概率解码不包含条件分支,而是无条件地从code
中减去range
,并使用结果的符号位来决定返回的比特,并生成一个掩码,该掩码与code
结合并加到range
上。
注意事项
- 在计算
bound
时,2^11的除法和向下取整操作是在乘法之前完成的,而不是之后(显然是为了避免需要快速硬件支持32位乘法和64位结果)。 - 固定概率解码并不严格等同于任何
prob
值的上下文基础范围解码,因为上下文基础范围解码在乘以prob
之前会丢弃range
的低11位,而固定概率解码只丢弃最后一位。
让我用更通俗的语言来解释LZMA解压缩算法的工作原理。
想象一下,有一个快递分拣中心,它的工作是将包裹(数据)快速准确地分发到正确的目的地。这个分拣中心有一个特殊的机器,我们称之为"范围解码器",它负责读取包裹上的标签(压缩数据),然后决定每个包裹应该去哪里。
-
初始化 :首先,分拣中心的机器会设置一个初始的范围(
range
),这就像是它知道包裹可能来自的区域大小。同时,它还会读取包裹上的一个初始标签(code
),这就像是包裹的起始位置。 -
归一化:如果包裹的区域变得太小,机器就需要重新调整(归一化),以便更准确地处理。这就像是放大地图,以便更清楚地看到细节。
-
上下文基础的范围解码 :机器会根据包裹上的标签(上下文)来预测包裹应该去哪里。它有一个概率值(
prob
),这个值帮助它预测包裹是去A地还是B地。如果预测是去A地,它就会调整标签和概率值,然后告诉工人这个包裹应该去A地。如果是去B地,它也会做相应的调整。 -
固定概率范围解码:有时候,机器不需要看包裹上的标签,而是直接根据一个固定的概率(比如50%)来决定包裹的去向。这就像是抛硬币决定包裹的去向。
- 如果区域太小,机器就会放大区域(归一化),然后继续工作。
- 如果需要根据上下文预测,机器会看包裹上的标签和概率值,然后决定包裹的去向。
- 如果不需要看上下文,机器就直接根据固定的概率来决定包裹的去向。
LZMA解压缩算法就像是这个快递分拣中心的工作流程。它通过一个智能的"范围解码器"来逐个比特地解读压缩数据,然后根据数据的特征(上下文)来预测和确定数据应该恢复成什么样子。这个过程需要不断地调整和预测,以确保数据能够被正确且高效地解压缩。
解码树
好的,我来详细解释并翻译关于范围解码器如何解码整数的内容。
范围解码器的整数解码功能
范围解码器提供了位树(bit-tree)、反向位树(reverse bit-tree)和固定概率整数解码(fixed probability integer decoding)功能,这些功能用于解码整数,并将上述的单比特解码推广到更广泛的应用。
-
位树解码(Bit-tree decoding):
- 为了解码小于某个限制值(limit)的无符号整数,需要提供一个包含(limit - 1)个11位概率变量的数组,这些变量在概念上被排列成一个完整的二叉树的内部节点,而树的叶子节点则是limit个。
- 非反向位树解码通过保持指向变量树的指针开始,这个指针从树根开始。只要指针不指向叶子节点,就使用指针指示的变量解码一个比特,然后根据比特是0还是1,将指针移动到左或右子节点;当指针指向叶子节点时,返回与该叶子节点关联的数字。
-
反向位树解码(Reverse bit-tree decoding):
- 反向位树解码则是从最低有效位到最高有效位进行解码,因此只支持范围是2的幂的整数,并且总是解码相同数量的比特。这等同于使用2的幂限制进行非反向位树解码,并将结果的最后log2(limit)位反转。
-
固定概率整数解码(Fixed probability integer decoding):
- 固定概率整数解码简单地重复执行固定概率比特解码,从最高有效位到最低有效位读取比特。
Linux内核中的实现
在Linux内核中的rc_bittree
函数,实际上返回的整数范围是[limit, 2 * limit)(将limit加到概念值上),数组中索引0的变量未使用,而索引1的是根节点,左和右子节点的索引计算为2i和2i + 1。rc_bittree_reverse
函数则将[0, limit)范围内的整数加到调用者提供的变量上,其中limit隐含地由其对数表示,并且为了效率原因有自己的独立实现。
范围解码器通过位树和反向位树解码功能来解码整数,这些功能允许从不同的角度(从最高位到最低位或从最低位到最高位)解码整数。固定概率整数解码则是在每个比特位置上应用相同的固定概率来解码整数。这些方法使得LZMA算法能够灵活且高效地处理压缩数据中的整数。
我来用更通俗的语言解释一下范围解码器是如何解码整数的。
想象一下,你有一个魔法盒子,这个盒子能够根据你提供的线索(概率变量)来猜测一个数字。这个数字是你想要从一堆数字中选择的一个,这些数字就像是树上的叶子,而线索则是树干和树枝。
-
非反向位树解码(Non-reverse bit-tree decoding):
- 这个过程就像是你在森林中寻找一个特定的树。你从树根(概率变量数组的开始)开始,根据你手中的地图(比特值)来决定是向左走还是向右走。
- 每走一步,你都会根据当前位置的线索(概率变量)来决定下一步的方向。如果你的地图告诉你"0",你就向左走;如果是"1",你就向右走。
- 当你走到叶子(概率变量数组的末尾)时,你就找到了你要找的数字。
-
反向位树解码(Reverse bit-tree decoding):
- 这个过程和非反向位树解码相反,你从叶子开始,向树根方向走。这意味着你从最低位开始解码,一直到最高位。
- 因为你是从下往上走,所以你只能走那些能够整除的路径(即范围是2的幂)。每次你走完全程,你都会得到一个数字。
-
固定概率整数解码(Fixed probability integer decoding):
- 这个就像是你在没有地图的情况下穿越森林,你只能随机选择方向。每次你都会抛硬币(50%的概率)来决定是向左走还是向右走。
- 你从最高位开始,一直到最低位,每一步都是独立的,不受之前步骤的影响。
LZMA解压缩算法中的范围解码器通过这些方法来解码整数。非反向位树解码让你从最高位开始,一步步解码到最低位;反向位树解码则是从最低位开始,一步步解码到最高位;固定概率整数解码则是每一步都有相同的概率,不受之前步骤的影响。这些方法让解压缩过程更加灵活和高效。
在解释为什么反向位树解码只能走那些能够整除的路径(即范围是2的幂)之前,我们先来理解一下反向位树解码的基本原理。
为什么只能走2的幂范围
-
简化计算 :在反向位树解码中,我们通常处理的是2的幂范围,因为这样可以简化计算。当我们说一个范围是2的幂时,意味着这个范围可以被表示为[2^n],其中(n)是一个整数。这样的范围在二进制表示中非常直观,因为它们对应于连续的二进制位,例如[2^3 = 8]在二进制中就是1000,这意味着从000到111(即0到7)。
-
固定的比特数:反向位树解码总是解码固定数量的比特。例如,如果范围是[2^3 = 8],那么无论何时,我们都需要解码3位来确定一个整数。这是因为在2的幂范围内,每个整数的二进制表示长度是固定的。
-
效率和一致性:使用2的幂范围可以提高解码效率,因为我们可以预先知道需要解码的比特数,这使得解码过程更加一致和可预测。如果范围不是2的幂,那么在解码过程中可能需要动态地调整比特数,这会增加计算复杂性。
-
二叉树的结构:在反向位树解码中,我们使用一个完整的二叉树来表示所有可能的整数。如果范围不是2的幂,那么在树的某些部分可能会出现不完整的节点,这会导致解码过程变得复杂。而2的幂范围确保了树的每个层级都是完整的,这简化了解码逻辑。
反向位树解码之所以只能走那些能够整除的路径(即范围是2的幂),是因为这样可以简化计算,确保固定的比特数,提高效率和一致性,并且保持二叉树结构的完整性。这些因素共同使得反向位树解码在处理整数时更加高效和可靠。
压缩参数
LZMA压缩算法的配置涉及到两个主要参数:一个"属性"字节(lclppb)和一个字典大小。这些参数决定了压缩算法的行为和效率。下面我将详细解释这些参数的含义和如何配置LZMA。
lclppb属性字节
LZMA的配置通过一个称为"属性"字节的lclppb来设置,这个字节是一个复合值,由三个部分组成:lc、lp和pb。这个字节的值计算公式是:
[ \text{lclppb} = \text{lc} + \text{lp} \times 9 + \text{pb} \times 9 \times 5 ]
-
lc(Literal Context bits):这是用于字面量编码的前一个字节的高位数。在LZMA SDK中,默认值是3。lc的值决定了在编码字面量时,上下文的复杂度。较高的lc值意味着更多的上下文被考虑在内,这可能会提高压缩率,但也会增加编码的复杂度。
-
lp(Literal Position bits):这是字典位置的低位数,用于字面量位置状态(literal_pos_state)。在LZMA SDK中,默认值是0。lp的值影响字面量的位置如何影响编码决策。
-
pb(Position bits):这是字典位置的低位数,用于位置状态(pos_state)。在LZMA SDK中,默认值是2。pb的值影响匹配位置的距离如何影响编码决策。
配置限制
- 在非LZMA2流中,lc的值不能大于8,lp和pb的值不能大于4。
- 在LZMA2流中,lc + lp的和以及pb的值都不能大于4。
7-zip LZMA文件格式中的配置
在7-zip的LZMA文件格式中,配置是通过一个包含"属性"字节的头部来完成的,紧接着是一个32位的小端序字典大小(以字节为单位)。这意味着字典的大小是在文件的头部明确指定的,而属性字节则包含了lc、lp和pb的配置信息。
LZMA2流中的配置
在LZMA2流中,属性字节可以在LZMA2的LZMA数据包开始时选择性地改变,而字典大小则在LZMA2头部中指定。这意味着在LZMA2流中,可以在不同的数据包中使用不同的压缩配置,提供了更大的灵活性。
LZMA的配置涉及到对压缩算法行为的精细控制,通过调整lc、lp和pb的值,可以影响压缩率和解压缩速度。在7-zip的LZMA文件格式中,这些配置信息被包含在文件的头部,而在LZMA2流中,可以在流的不同部分使用不同的配置,以适应不同的压缩需求。
压缩过程
LZMA编码上下文涉及到LZMA是如何统计建模LZ编码流的,也就是说,它决定了哪些概率变量会传递给范围解码器来解码每个比特。
概率变量和多维数组
这些概率变量实现为多维数组。在介绍这些数组之前,我们先定义一些用作这些多维数组索引的值。
状态值(state)
状态值基于最近看到的2-4个数据包类型与下表中的模式匹配情况,实现为每次输出数据包时根据表中的转换表更新的状态机状态。
- 初始状态是0,因此假定开始之前的包是LIT包。
状态 | 前4个包 | 前3个包 | 前2个包 | 前1个包 | 下一个包是 | 下一个包是 | 下一个包是 | 下一个包是 |
---|---|---|---|---|---|---|---|---|
LIT | MATCH | LONGREP[*] | SHORTREP | |||||
0 | LIT | LIT | LIT | 0 | 7 | 8 | 9 | |
1 | MATCH | LIT | LIT | 0 | 7 | 8 | 9 | |
2 | LONGREP[*] | LIT | LIT | 0 | 7 | 8 | 9 | |
*MATCH | SHORTREP | |||||||
3 | LIT | SHORTREP | LIT | LIT | 0 | 7 | 8 | 9 |
4 | MATCH | LIT | 1 | 7 | 8 | 9 | ||
5 | LONGREP[*] | LIT | 2 | 7 | 8 | 9 | ||
*MATCH | SHORTREP | |||||||
6 | LIT | SHORTREP | LIT | 3 | 7 | 8 | 9 | |
7 | LIT | MATCH | 4 | 10 | 11 | 11 | ||
8 | LIT | LONGREP[*] | 5 | 10 | 11 | 11 | ||
9 | LIT | SHORTREP | 6 | 10 | 11 | 11 | ||
10 | *MATCH | MATCH | 4 | 10 | 11 | 11 | ||
11 | *MATCH | *REP | 5 | 10 | 11 | 11 |
pos_state和literal_pos_state
- pos_state 和 literal_pos_state 分别由字典位置的pb和lp(来自LZMA头部或LZMA2属性包)最低有效位组成。字典大小通常是2的幂的倍数,所以这些值等同于自上次字典重置以来未压缩字节数的最低有效位。
prev_byte_lc_msbs
- prev_byte_lc_msbs 设置为前一个未压缩字节的lc(来自LZMA头部或LZMA2属性包)最高有效位。
is_REP
- is_REP 表示包含长度的数据包是LONGREP而不是MATCH。
match_byte
- match_byte 是如果使用SHORTREP数据包将被解码的字节(换句话说,是在最后使用的距离处字典中找到的字节);它只在*MATCH数据包之后使用。
literal_bit_mode
- literal_bit_mode 是一个包含8个值的数组,每个字节的每个比特位置都有一个值,如果前一个数据包是*MATCH,并且它是最高有效位或者所有更高有效位在待编码/解码的字面量中的比特与match_byte中相应位置的比特相等,则值为1或2,否则为0;1或2的选择取决于match_byte中相同位置的比特值。
字面量/Literal集合
- 字面量/Literal集合的变量可以看作是一个"伪位树",类似于位树,但是每个节点中有3个变量而不是1个,这些变量根据下一个要解码的比特的literal_bit_mode值在节点表示的位树上下文后选择。
通过这些复杂的上下文和状态机,LZMA能够有效地对数据流进行建模,并使用这些模型来指导范围解码器如何解码压缩数据。这种建模方法使得LZMA能够适应不同的数据模式,从而实现高效的压缩。
假设场景
假设我们正在压缩一个简单的文本文件,文件内容是"Moonshot AI is amazing"。我们将通过LZMA压缩算法的视角来看这个过程。
-
状态值(state):我们从状态0开始,这意味着在开始压缩之前,我们假设前一个数据包是LIT(字面量)。
-
字典位置(pos_state 和 literal_pos_state):我们维护一个字典位置,这个位置是自上次字典重置以来编码的字节数。例如,如果我们的字典大小是4096字节,那么pos_state和literal_pos_state将基于这个位置的最低有效位来确定。
-
前一个字节(prev_byte_lc_msbs):我们记录前一个未压缩字节的最高有效位。例如,如果前一个字节是'M'(二进制01001101),那么lc=4时,我们可能取其最高4位作为上下文。
-
is_REP:当我们遇到重复的数据时,我们需要决定是使用LONGREP还是MATCH。例如,如果我们已经编码了"Moonshot",再次遇到"Moonshot"时,我们可以使用LONGREP。
-
match_byte:如果我们使用SHORTREP,那么match_byte将是"Moonshot"中最后一个'T'的字节值。
-
literal_bit_mode:对于每个字节,我们根据前一个数据包是MATCH还是LIT,以及当前字节的最高位是否与match_byte相同,来设置literal_bit_mode的值。
-
伪位树(Literal set of variables):对于每个字节,我们使用literal_bit_mode来决定如何编码。例如,如果我们正在编码'A',并且前一个字节是'M',我们可能会根据literal_bit_mode的值来决定是直接编码'A',还是将其与'M'进行比较后再编码。
假设我们正在编码"is amazing"这部分文本,并且"Moonshot"已经被编码过。
- 状态值:我们从状态0开始,因为"Moonshot"的最后一个包是LIT。
- 字典位置:假设当前位置是1024,那么pos_state和literal_pos_state将基于1024的最低有效位。
- 前一个字节:如果我们刚刚编码了'M',那么prev_byte_lc_msbs将是'M'的最高有效位。
- is_REP:如果"is"中的'i'与"Moonshot"中的'i'位置相同,我们可以使用LONGREP。
- match_byte:如果使用SHORTREP,那么match_byte将是'M'。
- literal_bit_mode:对于'i',如果前一个包是MATCH,并且'i'的最高位与'M'相同,literal_bit_mode将设置为1或2。
- 伪位树:我们根据literal_bit_mode的值来决定如何编码'i'。如果literal_bit_mode是1,我们可能直接编码'i';如果是2,我们可能将其与'M'进行比较后再编码。
通过这种方式,LZMA算法能够根据上下文信息来优化编码决策,从而实现更高效的压缩。这个过程中,状态机和多维数组的使用使得算法能够适应不同的数据模式,并且动态调整编码策略。