我想知道Java HashMap和ArrayList相比的内存开销有多大?
更新:
我希望提高对大型包(6百万个以上)相同对象的特定值搜索速度。
因此,我考虑使用一个或多个HashMap代替使用ArrayList。但我想知道HashMap的开销是多少。
据我所了解,它不存储键本身,只存储键的哈希值,因此它应该类似于对象哈希大小+一个指针。
但使用的是什么哈希函数?是Object提供的那个还是另一个?
我想知道Java HashMap和ArrayList相比的内存开销有多大?
更新:
我希望提高对大型包(6百万个以上)相同对象的特定值搜索速度。
因此,我考虑使用一个或多个HashMap代替使用ArrayList。但我想知道HashMap的开销是多少。
据我所了解,它不存储键本身,只存储键的哈希值,因此它应该类似于对象哈希大小+一个指针。
但使用的是什么哈希函数?是Object提供的那个还是另一个?
public class Payload {
int key,b,c;
Payload(int _key) { key = _key; }
}
import org.junit.Test;
import java.util.HashMap;
import java.util.Map;
public class Overhead {
@Test
public void useHashMap()
{
int i=0;
try {
Map<Integer, Payload> map = new HashMap<Integer, Payload>();
for (i=0; i < 4000000; i++) {
int key = (int)(Math.random() * Integer.MAX_VALUE);
map.put(key, new Payload(key));
}
}
catch (OutOfMemoryError e) {
System.out.println("Got up to: " + i);
}
}
@Test
public void useArrayList()
{
int i=0;
try {
ArrayListMap map = new ArrayListMap();
for (i=0; i < 9000000; i++) {
int key = (int)(Math.random() * Integer.MAX_VALUE);
map.put(key, new Payload(key));
}
}
catch (OutOfMemoryError e) {
System.out.println("Got up to: " + i);
}
}
}
import java.util.ArrayList;
public class ArrayListMap {
private ArrayList<Payload> map = new ArrayList<Payload>();
private int[] primes = new int[128];
static boolean isPrime(int n)
{
for (int i=(int)Math.sqrt(n); i >= 2; i--) {
if (n % i == 0)
return false;
}
return true;
}
ArrayListMap()
{
for (int i=0; i < 11000000; i++) // this is clumsy, I admit
map.add(null);
int n=31;
for (int i=0; i < 128; i++) {
while (! isPrime(n))
n+=2;
primes[i] = n;
n += 2;
}
System.out.println("Capacity = " + map.size());
}
public void put(int key, Payload value)
{
int hash = key % map.size();
int hash2 = primes[key % primes.length];
if (hash < 0)
hash += map.size();
do {
if (map.get(hash) == null) {
map.set(hash, value);
return;
}
hash += hash2;
if (hash >= map.size())
hash -= map.size();
} while (true);
}
public Payload get(int key)
{
int hash = key % map.size();
int hash2 = primes[key % primes.length];
if (hash < 0)
hash += map.size();
do {
Payload payload = map.get(hash);
if (payload == null)
return null;
if (payload.key == key)
return payload;
hash += hash2;
if (hash >= map.size())
hash -= map.size();
} while (true);
}
}
最简单的方法是查看源代码并以此方式解决。然而,您真正比较的是苹果和橙子-列表和映射在概念上是非常不同的。很少会根据内存使用情况来选择它们之间的差异。
这个问题背后的背景是什么?
存储在这两者中的都是指针。根据你的架构,指针应该是32位或64位(或更多或更少)。
10个元素的数组列表最少会分配10个“指针”(还有一些一次性开销)。
映射表必须分配两倍的空间(20个指针),因为它同时存储两个值。然后,在此基础上,它还必须存储“哈希”。在75%的负载下,哈希应该大于映射表,大约为13个32位值。
所以,如果您想要一个不经思考的答案,比率应该是1:3.25左右,但您只谈到了指针存储--除非您存储了大量对象,否则非常小--如果是这样,能够即时引用(HashMap)与迭代(数组)之间的效用应该比内存大小重要得多。
哦,还有:
数组可以适应您集合的确切大小。如果指定大小,哈希映射表也可以,但是如果超出该大小,则会重新分配较大的数组,并且不使用其中的一些空间,因此也可能会有一些浪费。
public static void displayMemory() {
Runtime r=Runtime.getRuntime();
r.gc();
r.gc(); // YES, you NEED 2!
System.out.println("Memory Used="+(r.totalMemory()-r.freeMemory()));
}
HashMap持有对值和键的引用。
ArrayList只持有对值的引用。
因此,假设键使用相同的内存作为值,则 HashMap 使用 50% 更多的内存(严格来说,并不是 HashMap 使用了那些内存,因为它只保留对其的引用)
另一方面,HashMap 提供基本操作(get 和 put)的常数时间性能。因此,尽管它可能使用更多内存,但使用 HashMap 比使用 ArrayList 快得多。
所以,你应该不关心谁使用更多内存,而是关注它们的适用场景。
使用正确的数据结构可以比底层库的实现方式节省更多的 CPU/内存。
编辑
在 Grant Welch 的回答之后,我决定测试 2,000,000 个整数。
这是源代码。
这是输出结果:
$
$javac MemoryUsage.java
Note: MemoryUsage.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
$java -Xms128m -Xmx128m MemoryUsage
Using ArrayListMemoryUsage@8558d2 size: 0
Total memory: 133.234.688
Initial free: 132.718.608
Final free: 77.965.488
Used: 54.753.120
Memory Used 41.364.824
ArrayListMemoryUsage@8558d2 size: 2000000
$
$java -Xms128m -Xmx128m MemoryUsage H
Using HashMapMemoryUsage@8558d2 size: 0
Total memory: 133.234.688
Initial free: 124.329.984
Final free: 4.109.600
Used: 120.220.384
Memory Used 129.108.608
HashMapMemoryUsage@8558d2 size: 2000000
无论哪种情况,我建议在开始添加之前先确定预期数据的大小。这将给实现一个更好的初始设置,并且在两种情况下都可能消耗更少。
更新:
根据您的更新问题,请查看Glazed lists。这是由一些Google人编写的一个非常快速的类似于您描述的操作的小工具。允许聚类、过滤、搜索等操作。
基本上,在使用中应该选择“正确的工具”。由于有不同的实例需要键/值对(可能使用 HashMap
),而有些实例只需要一组值(可能使用 ArrayList
),所以“哪个使用更多内存”的问题,在我看来是无关紧要的,因为这不是选择其中一个而不是另一个的考虑因素。
但是回答这个问题,由于 HashMap
存储键/值对,而 ArrayList
只存储值,我会假设仅添加键到 HashMap 中就意味着它占用更多内存,当然,我们比较它们时正在比较相同的值 类型(例如,两者的值都是字符串)。
ArrayList
的操作都是 O(1)
,但可能不包括您用于查找与特定值匹配的对象的操作。
如果您正在遍历 ArrayList
中的每个元素并测试相等性,或使用 contains()
,那么这意味着您的操作运行时间为 O(n)
(或更差)。
如果您不熟悉 O(1)
或 O(n)
符号,这是指操作需要多长时间。在这种情况下,如果您可以获得常量时间性能,您就要利用它。如果 HashMap.get()
是 O(1)
,这意味着检索操作需要大致相同的时间,而不管 Map 中有多少条目。
像 ArrayList.contains()
这样的操作是 O(n)
,这意味着它所需的时间随列表大小增加而增加;因此,在具有六百万条目的 ArrayList
上进行迭代将不会非常有效。
我不知道确切的数字,但HashMap比较重。相比之下,ArrayList的内部表示是自证的,但HashMap保留了Entry对象,这可能会使您的内存消耗急剧增加。
它并不是很大,但确实比较大。一个很好的可视化方法是使用动态分析器,例如YourKit,它允许您查看所有堆分配。这非常不错。