压缩数字数组

时间:2014-05-28 12:47:00

标签: java arrays performance compression

我有一个大数组(~400.000.000个条目),整数为{0,1,...,8}。 所以每个条目我需要4位。大约200 MB。

目前我使用字节数组并在每个条目中保存2个数字。

我想知道,如果有一个好的方法,压缩这个数组。我做了一个快速研究,发现了像Huffmann或LZW这样的算法。但这些算法都用于压缩数据,将压缩数据发送给某人并解压缩。

我只想拥有一个具有较少内存空间的表,因此我可以将其加载到RAM中。 200MB的桌子很容易适合,但我正在考虑更大的桌子。

重要的是,我仍然能够确定某些位置的值。

任何提示?


更多信息: 我刚做了一点实验,事实证明,平均2.14个连续数字具有相同的值。 有1个零,154个,10373个,385990个三分,8146188个,85008968个,265638366个六,70791576个七和80个八个。 所以超过一半的数字是6s。

我只需要一个快速的getValue(idx)功能,setValue(idx,value)并不重要。

6 个答案:

答案 0 :(得分:4)

这取决于您的数据的样子。是否有重复的条目,或者它们只是缓慢变化,还是什么?

如果是这样,您可以尝试压缩数据块并在需要时解压缩。块越大,可以节省的内存越多,速度越差。恕我直言没什么好处。您还可以将数据压缩并解压缩到内存中。

否则,即在没有规律的情况下,每个条目至少需要log(9) / log(2) = 3.17位,并且没有什么可以改善它。

通过将5个数字打包到short,您可以非常接近此值。如9**5 = 59049 < 65536 = 2**16,它几​​乎完美。你需要每个数字3.2位,没有大赢。通过该公式给出五个数字的包装

a + 9 * (b + 9 * (c + 9 * (d + 9 * e)))

并通过预先计算的表解压缩。

问题更新后

更新

  

进一步的信息:我刚做了一点实验,结果表明平均2.14个连续数字具有相同的值。有1个零,154个,10373个,385990个三分,8146188个,85008968个,265638366个六,70791576个七和80个八个。所以超过一半的数字是6s。

事实上,平均大约有2.14个连续数字是相同的,可能导致一些压缩,但在这种情况下它没有说什么。几乎只有五个和六个,所以似乎暗示了两个相等的连续数字。

鉴于这一事实,你可以忘记我的上述优化。由于您可以单独处理单个零,因此实际上只有8个值。因此,每个值只需要3位,零点需要一个索引。

您甚至可以为低于4或高于7的所有值创建HashMap,存储1 + 154 + 10373 + 385990 + 80个条目,每个值仅使用2位。但这还远非理想。

假设没有规律性,每个值需要1.44位,因为这是entropy。您可以遍历所有5元组,计算它们的直方图,并使用1个字节来编码255个最频繁的元组。剩下的所有元组都将映射到第256个值,告诉您必须在HashMap中查找稀有元组值。

一些评估

我很好奇它能不能起作用。将5个数字打包成一个字节需要85996340个字节。有近500万个元组不适合,所以我的想法是为它们使用哈希映射。假设重新开始而不是链接它是有意义的保持它可能50%满,所以我们需要1000万条目。假设TIntShortHashMap(将索引映射到元组),每个条目占用6个字节,从而导致60 MB。太糟糕了。

仅将4个数字打包到一个字节中消耗107495425个字节,并留下159531个不适合的元组。这看起来更好,但是,我确信更密集的包装可以得到很大改善。

这个小program生成的结果:

*** Packing 5 numbers in a byte. ***
Normal packed size: 85996340.
Number of tuples in need of special handling: 4813535.

*** Packing 4 numbers in a byte. ***
Normal packed size: 107495425.
Number of tuples in need of special handling: 159531.

答案 1 :(得分:1)

有很多选择 - 大多数取决于数据的外观。您可以使用以下任何一种甚至是它们的组合。

LZW - or variants

在您的情况下,使用4位初始字典的变体可能是一个好的开始。

您可以在块中压缩数据,这样您就可以使用请求的索引来确定要动态解码的块。

如果数据中存在重复模式,这将非常适合。

差异编码

您的编辑表明您的数据可能会受益于差异传递。基本上,你用它和它的前身之间的差异来替换每个值。

同样,您需要以块和不同的固定运行长度处理数据。

您可能还会发现使用LZW的差分跟踪将是一个很好的解决方案。

Fourier Transform

如果某些数据丢失是可以接受的,那么一些傅立叶变换压缩方案可能会有效。

Lossless JPEG

如果你的数据具有二维方面,那么一些JPEG算法可能会很好地运用它们。

底线

你需要牢记:

  1. 压缩时间越长 - 达到极限 - 您可以实现更好的压缩率
  2. 对无损压缩的程度有一个实际的限制。
  3. 一旦你有损,你基本上不再受限制。您可以使用new int[]{6}来估算整个数据,并获得一些正确的结果。

答案 2 :(得分:1)

由于超过1/2的条目是六个,所以只需将它们编码为一个比特。使用2位表示第二常用,依此类推。然后你有这样的事情:

                        encoding               total   
           #entrie      bit pattern  #bits    # of bits
 zero            1      000000001      9              9
 ones          154      0000001        7           1078  
 twos        10373      000001         6          62238
 threes     385990      00001          5        1929950
 fours     8146188      0001           4       32584752
 fives    85008968      01             2      170017936
 sixes   265638366      1              1      265638366
 sevens   70791576      001            3      212374728
 eights         80      00000001       8            640
--------------------------------------------------------
 Total                                        682609697 bits

如果429981696条目编码为682609697位,则平均每个条目需要 1.59位

编辑:

为了允许快速查找,您可以对压缩数据进行索引,以显示每个条目的开始位置。然后,找到确切的值将涉及平均解压缩n / 2个条目。根据应该的速度,您可以调整索引中的条目数。要将指针的大小减小到压缩数据(以及索引的大小),请使用估计值并仅存储该估计值的偏移量。

                                Estimated pos   Offset from
# entry no   Actual Position     (n * 1.59)      estimated
     0             0                  0               0      
   100           162                159               3      Use this 
   200           332                318              14  <-- column as   
   300           471                477              -6      the index
   400           642                636               6
   500           807                795              12
   600           943                954             -11

每100个条目和10个偏移量的索引的开销,将意味着每个条目额外 0.1位

答案 3 :(得分:1)

  

有1个零,154个,10373个,385990个三分,8146188个,   85008968五人,265638366六人,70791576七人制和八十八人

总计= 429981696个符号

假设分布是随机的,熵定理表示你不能做得比618297161.7位~73.707 MB或平均1.438位/符号好。

最小位数为SUM(count [i] * LOG(429981696 / count [i],2))。

您可以使用范围编码器实现此尺寸。

给定Sqrt(N)= 20736

通过在每个SQRT之后用算术解码器状态保存int [k = 0 .. CEIL(SQRT(N)) - 1]状态,你可以再次实现访问随机元素的O(Sqrt(N))复杂度( N)解码符号。这允许快速解码下一个20736符号块。

如果以线性方式访问内存流,访问元素的复杂性将降至O(1)。

使用的附加内存:20736 * 4 = 81KB。

答案 4 :(得分:0)

您应该查看BitSet以最有效地存储它。与名称所暗示的相反,它不是一个集合,它有订单,您可以按索引访问它。

在内部,它使用long的数组来存储这些位,因此可以使用位掩码自行更新。

我不相信你可以在本地更有效地存储它,如果你想要更高的效率,那么你应该考虑打包/压缩算法。

答案 5 :(得分:0)

如何考虑某些缓存解决方案,例如mapdbapache jcs。这将使您能够将Collection保留到磁盘,从而使您可以使用非常大的列表。