我有一个大数组(~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)并不重要。
答案 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)
有很多选择 - 大多数取决于数据的外观。您可以使用以下任何一种甚至是它们的组合。
在您的情况下,使用4位初始字典的变体可能是一个好的开始。
您可以在块中压缩数据,这样您就可以使用请求的索引来确定要动态解码的块。
如果数据中存在重复模式,这将非常适合。
您的编辑表明您的数据可能会受益于差异传递。基本上,你用它和它的前身之间的差异来替换每个值。
同样,您需要以块和不同的固定运行长度处理数据。
您可能还会发现使用LZW的差分跟踪将是一个很好的解决方案。
如果某些数据丢失是可以接受的,那么一些傅立叶变换压缩方案可能会有效。
如果你的数据具有二维方面,那么一些JPEG算法可能会很好地运用它们。
你需要牢记:
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)
如何考虑某些缓存解决方案,例如mapdb或apache jcs。这将使您能够将Collection保留到磁盘,从而使您可以使用非常大的列表。