在Java中为对象实现内存中压缩

时间:2011-05-09 08:38:35

标签: java memory-management compression

我们有这个用例,我们希望压缩和存储对象(内存中)并在需要时解压缩它们。

我们想要压缩的数据是多种多样的,从浮动向量到字符串到日期。

有人可以提出任何好的压缩技术吗?

我们正在考虑压缩的容易程度和减压速度是最重要的因素。

感谢。

8 个答案:

答案 0 :(得分:52)

如果要压缩MyObject的实例,可以让它实现Serializable,然后将对象流式传输到压缩字节数组中,如下所示:

ByteArrayOutputStream baos = new ByteArrayOutputStream();
GZIPOutputStream gzipOut = new GZIPOutputStream(baos);
ObjectOutputStream objectOut = new ObjectOutputStream(gzipOut);
objectOut.writeObject(myObj1);
objectOut.writeObject(myObj2);
objectOut.close();
byte[] bytes = baos.toByteArray();

然后将byte[]解压缩回对象:

ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
GZIPInputStream gzipIn = new GZIPInputStream(bais);
ObjectInputStream objectIn = new ObjectInputStream(gzipIn);
MyObject myObj1 = (MyObject) objectIn.readObject();
MyObject myObj2 = (MyObject) objectIn.readObject();
objectIn.close();

答案 1 :(得分:8)

与之前的答案类似,除了我建议你使用DeflatorOutputStream和InflatorInputStream,因为它们比替代品更简单/更快/更小。它更小的原因是它只是进行压缩,而替代方案则添加文件格式扩展,如CRC校验和标题。

如果大小很重要,您可能希望对自己进行简单的序列化。这是因为ObjectOutputStream具有显着的开销,使得小对象更大。 (对于较大的对象,特别是在压缩时会有所改进)

e.g。一个Integer占用81个字节,压缩对于这么少的字节数没什么用。有可能显着减少这种情况。

答案 2 :(得分:7)

一个建议可能是使用以下流的组合:

答案 3 :(得分:3)

Java中的searilized对象的压缩通常不太好......不太好。

首先,您需要了解Java对象有很多不需要的附加信息。如果你有数百万个对象,你就会有数百万次这样的开销。

作为示例,我们可以使用双链表。每个元素都有一个前一个和一个下一个指针+你存储一个长值(时间戳)+字节用于交互类型和两个整数用于用户id。由于我们使用指针压缩,因此我们是6Bytes * 2 + 8 + 4 * 2 = 28Bytes。 Java为填充添加了8字节+ 12字节。这使得每个元素48Bytes。

现在我们创建1000万个列表,每个列表包含20个元素(过去三年中用户点击事件的时间序列(我们希望找到模式))。

所以我们有200万* 48字节的元素= 10GB内存(确实不多)。

好的旁边垃圾收集会杀死我们以及JDK内部的开销,我们以10GB内存结束。

现在让我们使用自己的内存/对象存储。我们将其存储为列式数据表,其中每个对象实际上是一行。因此,我们在时间戳,前一个,下一个,userIdA和userIdB集合中有200万行。

上一个和下一个现在指向行ID并变为4byte(如果超过40亿条目(不太可能),则为5bytes)。

所以我们有8 + 4 + 4 + 4 + 4 => 24 * 200 Mio = 4.8GB +无GC问题。

由于timestamp列以最小最大时间存储时间戳,并且我们的时间戳都在三年内,因此我们只需要5bytes来存储每个时间戳。由于指针现在相对存储(+和 - ),并且由于点击系列是及时密切相关的,因此前一个和下一个平均只需要2个字节,而对于用户ID,我们使用字典,因为点击系列适用于大约500k用户我们每个只需要三个字节。

所以我们现在有5 + 2 + 2 + 3 + 3 => 15 * 200Mio => 3GB + 4 * 500k * 4 = 8MB = 3GB + 8MB的字典。听起来不同于10GB吧?

但我们尚未完成。由于我们现在没有对象,只有行和数据,我们将每个系列存储为一个表行,并使用特殊列作为数组的集合,实际存储5个值,指向下五个值+前一个指针。

所以我们有10个10Mio列表,每个20个enry(因为我们有开销),我们有每个列表20 *(5 + 3 + 3)+ 4 * 6(让我们添加一些部分填充元素的开销)=> 20 * 11 + 5 * 6 => 250 * 10Mio => 2,5GB +我们可以比步行元素更快地访问阵列。

但是它尚未结束......时间戳现在相对存储,每个条目只需要3个字节,第一个条目需要5个字节。 - >所以我们节省了更多20 * 9 + 2 + 5 * 6 => 212 * 10Mio => 2,12 GB。现在使用gzip将它全部存储到内存中,我们得到1GB,因为我们可以将它全部存储起来,首先存储数组的长度,所有时间戳,所有用户ID都非常高,因为有些模式可以压缩。由于我们使用字典,我们只需根据每个userId的可行性对其进行排序即可成为系列的一部分。

由于所有东西都是一个表,你可以以几乎读取速度反序列化所有内容,因此现代SSD上的1GB需要2秒才能加载。尝试使用序列化/反序列化,您可以听到内部用户的哭声。

因此,在压缩序列化数据之前,将其存储在表中,检查每个列/属性是否可以在逻辑上进行压缩。最后玩得开心。

请记住1TB(ECC)今天要花费10k。没什么。和1TB SSD 340欧元。因此,除非你真的需要,否则不要在这个问题上浪费你的时间。

答案 4 :(得分:2)

我所知道的最好的压缩技术是ZIP。 Java支持ZipStream。您只需将对象序列化为字节数组然后压缩即可。

提示:使用ByteArrayOutputStream,DataStream,ZipOutputStream。

答案 5 :(得分:2)

JDK中实现了各种压缩算法。检查所有实施算法的[java.util.zip](http://download.oracle.com/javase/6/docs/api/java/util/zip/package-summary.html)。但是,压缩所有数据可能不是一件好事。例如,序列化的空数组可能是几十个字节,因为基础类的名称在序列化数据流中。此外,大多数压缩算法旨在消除大数据块的冗余。在中小型Java对象上,你可能几乎没有任何收益。

答案 6 :(得分:2)

这是一个棘手的问题:

首先,使用ObjectOutputStream可能不是答案。流格式包括许多与类型相关的元数据。如果要序列化小对象,强制元数据将使压缩算法难以“收支平衡”,即使您实现了自定义序列化方法。

使用DataOutputStream添加最少(或没有)类型信息会得到更好的结果,但混合数据通常不能使用通用压缩算法进行压缩。

为了更好地进行压缩,您可能需要查看正在压缩的数据的属性。例如:

    如果您知道精度为1天,则
  • Date个对象可以表示为int值。
  • int值的序列可以进行行程编码,如果它们具有正确的属性,则可以进行增量编码。
  • 等等。

无论如何,你需要做大量工作才能获得有价值的压缩量。 IMO,更好的想法是将对象写入数据库,数据存储区或文件,并使用缓存将常用对象保存在内存中。

答案 7 :(得分:1)

如果需要压缩任意对象,可能的方法是将对象序列化为字节数组,然后使用例如DEFLATE算法(GZIP使用的算法)来压缩它。当您需要该对象时,可以对其进行解压缩和反序列化。不确定这会有多高效,但它将是完全一般的。