为啥 Java 中的 HashSet 占用这么多内存?

Posted

技术标签:

【中文标题】为啥 Java 中的 HashSet 占用这么多内存?【英文标题】:Why is HashSet in Java taking so much memory?为什么 Java 中的 HashSet 占用这么多内存? 【发布时间】:2021-12-25 01:34:26 【问题描述】:

我正在将一个包含大约 3800 万行的 1GB ASCII 文本文件加载到 HashSet 中。使用 Java 11,该过程需要大约 8GB 的​​内存。 ​

HashSet<String> addresses = new HashSet<>(38741847);
​try (Stream<String> lines = Files.lines(Paths.get("test.txt"), Charset.defaultCharset())) 
    lines​.forEach(addresses::add);
​
​System.out.println(addresses.size());
​Thread.sleep(100000);

为什么 Java 占用这么多内存?

相比之下,我在 Python 中实现了相同的东西,只占用 4GB 内存。

s = set()
with open("test.txt") as file:
for line in file:
    s.add(line)
print(len(s))
time.sleep(1000)

【问题讨论】:

A HashSet 通常有 32 字节/元素的开销,对于初学者来说...... 什么版本的Java?使用 Java 9 或更高版本可能会cut in half 用于表示您的文本的内存量。 @BasilBourque 我正在使用 Java 11 【参考方案1】:

同时我找到了答案here,在那里我还发现了一些替代的 HashSet 实现,它们是 trove4j、hppc 和 Guava 库。我已经用相同的代码测试了它们。结果如下:

trove4j (5.5GB)

THashSet<String> s = new THashSet<>(38742847,1);

hppc (4.7GB)

ObjectHashSet <String> s2 = new ObjectHashSet<>(38742847,1, 0.99); 

番石榴 (5GB)

ImmutableSet<String> s2    
ImmutableSet.Builder<String> b =  ImmutableSet.builder();
lines.forEach(b::add);
s2 =b.build();

我决定使用 Guava,因为它不需要知道要插入的元素的确切数量。所以我不必先计算文件的行数。

【讨论】:

ObjectIdentityHashSet 不会有帮助,因为它不会进行重复数据删除或任何其他操作。你也可以试试 Guava ImmutableSet,它的开销大约是 HashSet 的三分之一。 @LouisWasserman 谢谢!它应该使用ObjectHashSet 而不是ObjectIdentityHashSet。我已经相应地更新了我的答案。【参考方案2】:

HashSet 的负载因子默认为 0.75。这意味着一旦哈希集已满 75%,就会重新分配内存。如果您的哈希集应该包含 38741847 个元素,则必须使用 38741847/0.75 对其进行初始化或设置更高的负载因子:

new HashSet<>(38741847, 1); // load factor 1 (100%)

【讨论】:

即使负载系数为 1,也需要 7.4 GB。

以上是关于为啥 Java 中的 HashSet 占用这么多内存?的主要内容,如果未能解决你的问题,请参考以下文章

Pytorch:为啥`tensor`变量占用的内存这么小?

HTML:为啥元素占用这么多宽度?

为啥 dequeue() 占用这么多处理器时间?

为啥MongoDB会占用这么多空间?

为啥 QML Image 占用这么多内存?减慢申请

为啥我的 UIImage 占用这么多内存?