Java 集合中的内存消耗
嗨,最近在一次采访中,有人问我有一个Hashmap、一个ArrayList和一个Hashset。它们各自包含相同的10个用户定义对象(例如:Employee类对象)。哪个会消耗更多堆空间?为什么 我给出的答案是Hashmap,因为它存储了两个键值对。但是Hashset也在内部使用hashmap来存储值Java 集合中的内存消耗,java,memory-management,collections,Java,Memory Management,Collections,嗨,最近在一次采访中,有人问我有一个Hashmap、一个ArrayList和一个Hashset。它们各自包含相同的10个用户定义对象(例如:Employee类对象)。哪个会消耗更多堆空间?为什么 我给出的答案是Hashmap,因为它存储了两个键值对。但是Hashset也在内部使用hashmap来存储值 有人能给我一个合理的答案吗 是否有任何工具或eclipse插件我可以用来检查自己 谢谢 如果您同时计算容器所需的内存和“10个用户定义对象”所需的内存,那么您是正确的 HashMap将占用更多空间
谢谢 如果您同时计算容器所需的内存和“10个用户定义对象”所需的内存,那么您是正确的
HashMap
将占用更多空间
尽管HashSet
由HashMap
支持,但它在所有条目中存储的值都是对同一虚拟实例的引用
因此,HashSet
需要10个键引用+10个值引用+10个元素+1个虚拟实例
另一方面,HashMap
需要10个键引用+10个值引用+10个键实例+10个值实例(假设“10个用户定义对象”存储为值)
当然,为了更准确,还必须计算包含HashMap
s bucket的数组的大小,但是HashMap
和HashSet
中的大小是相同的,因为它们包含相同数量的元素
请注意,正如JB Nizet所评论的,如果HashMap
的键是“10个用户定义对象”的属性,“10个键实例”不需要额外的内存(因为它们已经存在),因此HashMap
和HashSet
都需要相同的内存量来存储10个对象,由于HashSet
实际上需要更多的内存,因为它包含对HashMap
的引用
ArrayList
应该比HashSet
和HashMap
占用更少的内存,因为ArrayList
的备份数组的默认初始长度为10(足以存储10个对象),而HashMap
的存储桶数组的默认初始长度为16(假设我们使用默认负载系数0.75,也足以存储10个对象)。我发现这非常有趣,虽然我同意Eran的观点,但需要适当的证明。我正在使用
在本例中,我创建了一个Employee
,其中包含两个字段String name
和int age
让我们看看发生了什么:
List<Employee> list = new ArrayList<>();
list.add(new Employee(22, "a"));
System.out.println(GraphLayout.parseInstance(list).totalSize()); //152 bytes
出于示例的考虑,我接下来将添加2名员工,并缩短关于规模的解释:
HashMap<Employee, Integer> map = new HashMap<>();
map.put(new Employee(22, "a"), 100);
map.put(new Employee(23, "b"), 200);
System.out.println(GraphLayout.parseInstance(map).toFootprint());
总大小为368字节。现在,让我们将它们放入一个哈希集:
HashSet<Employee> set = new HashSet<>();
set.add(new Employee(22, "a"));
set.add(new Employee(23, "b"));
System.out.println(GraphLayout.parseInstance(set).totalSize()); // 368 bytes
HashSet=newhashset();
增加(新雇员(22,“a”);
增加(新雇员(23,“b”);
System.out.println(GraphLayout.parseInstance(set.totalSize());//368字节
您可以看到一个HashSet
和一个HashMap
对于这个特定场景具有相同的大小。如果您添加超过12个条目(默认情况下,HashMap
重新调整大小),事情会变得更加棘手并且可能会将它的bucket从LinkedNode
s更改为TreeNode
s,这两者之间存在差异。Node
是32字节,而TreeNode
是56字节。您的答案是正确的。任何文本编辑器都允许查看JDK附带的HashMap和HashSet的代码。@JBNizet但HashSet也存储为键值对(虽然本例中的值是常量)。所以它应该只有hashmap。对吗?因为我可以有任何大于hashset默认当前值的值。我不明白“它应该只有hashmap”或“我可以有任何大于默认当前值的值”是什么意思。我的意思是假设在散列映射中有一个字符串作为值,它占用300字节。现在在散列集中存在一个伪值(让它的大小为100字节)。因此,每次呈现的大小都是恒定的,但散列映射中的值大小可能会有所不同。我希望这一次我很清楚。考虑到他们只说映射中存储了员工,可以假设(我确实这么做)键是该员工的字段(例如,他的姓名或ID)。在这种情况下,没有额外的10个键实例,HashMap和HashSet消耗相同的内存量(实际上,对于HashSet来说要多一点,因为它需要对其备份HashMap的引用)。@JBNizet如果键是“用户定义对象”的属性那么你是对的。好的,因为问题是关于十个元素的,我根本不希望有任何容量增加操作。ArrayList的默认容量是十,所以所有元素都适合,HashMap
的默认容量是16
,负载因子是0.75
,所以它最多可以接受十二个元素当然,16
仍然大于10
,因此HashMap
/HashSet
在这种情况下具有更大的容量(在其他情况下,HashMap
在需要时将容量加倍,而ArrayList
使用系数1.5)@Eran我喜欢这个答案,但我真的很想证明,所以我给你的添加了一个小的扩充…很好的分析!但是,你得到的HashSet
和HashMap
的368字节结果不是巧合吗?毕竟,你的HashMap
示例还有两个Integer
实例,但另一方面, HashSet
示例具有额外的私有瞬态HashMap;
引用(以及同时具有HashSet
和HashMap
实例的开销)。如果放置/添加10个条目/元素,会得到相同的结果吗?@Eran一个好的捕获-我选择了这些值
COUNT AVG SUM DESCRIPTION
2 24 48 [C
1 80 80 [Ljava.util.HashMap$Node;
2 16 32 java.lang.Integer
2 24 48 java.lang.String
1 48 48 java.util.HashMap
2 32 64 java.util.HashMap$Node
2 24 48 org.erabii.tenelemdiff.Test$Employee
12 368 (total)
HashSet<Employee> set = new HashSet<>();
set.add(new Employee(22, "a"));
set.add(new Employee(23, "b"));
System.out.println(GraphLayout.parseInstance(set).totalSize()); // 368 bytes