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

Kic*_*chu 33 java compression memory-management

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

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

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

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

谢谢.

Whi*_*g34 54

如果要压缩实例,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();
Run Code Online (Sandbox Code Playgroud)

然后将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();
Run Code Online (Sandbox Code Playgroud)

  • 并非每个类都可以实现`Serializable`,特别是如果它是JDK类 (3认同)
  • 不要忘记`gzipOut.finish()` (2认同)

Pet*_*rey 9

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

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

例如,Integer需要81个字节,压缩对于如此少量的字节无济于事.可以显着减少这种情况.


Jon*_*und 7

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


Mar*_*ten 5

Java 中序列化对象的压缩通常不太好……不太好。

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

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

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

所以我们有 200Million * 48 Bytes 元素 = 10GB 内存(好吧,不多)。

好吧,除了垃圾收集会杀死我们以及 JDK 内部的开销猛增之外,我们最终还是选择了 10GB 内存。

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

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

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

Since the timestamp column stores the timestamps in a min max fashion and our timestamps all are within three years, we only need 5bytes to store each of the timestamps. Since the pointer are now stored relative (+ and -) and due the click series are timely closely related we only need 2bytes in average for both previous and next and for the user ids we use a dictionary since the click series are for roughly 500k users we only need three bytes each.

So we now have 5 + 2 + 2 + 3 + 3 => 15 * 200Mio => 3GB + Dictionary of 4 * 500k * 4 = 8MB = 3GB + 8MB. Sounds different to 10GB right?

但我们还没有完成。由于我们现在除了行和数据之外没有任何对象,因此我们将每个系列存储为表行,并使用特殊列作为数组集合,这些列实际上存储 5 个值和一个指向下五个值的指针 + 一个前一个值的指针。

所以我们有 10Mio 列表,每个列表有 20 个条目(因为我们有开销),每个列表有 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 欧元。因此,除非确实必要,否则不要在这个问题上浪费时间。