我想知道java HashMap与ArrayList相比的内存开销是多少?
更新
我想提高搜索大包装(600万+)相同物品的特定值的速度。
因此,我正在考虑使用一个或多个HashMap而不是使用ArrayList。但我想知道HashMap的开销是多少。
据我所知,密钥不存储,只存储密钥的哈希值,所以它应该是对象哈希值的大小+一个指针。
但是使用了什么哈希函数?是the one offered by Object还是其他人?
答案 0 :(得分:42)
如果您正在将HashMap与ArrayList进行比较,我假设您正在对ArrayList进行某种搜索/索引,例如二进制搜索或自定义哈希表...?因为.get(key)到600万个条目使用线性搜索是不可行的。
使用这个假设,我做了一些实证测试并得出结论:“如果你使用带有二进制搜索或自定义哈希映射实现的ArrayList,你可以在相同数量的RAM中存储2.5倍的小对象,与HashMap相比“。我的测试是基于只包含3个字段的小对象,其中一个是键,键是整数。我使用了32位的jdk 1.6。有关此图“2.5”的注意事项,请参见下文。
需要注意的关键事项是:
(a)它不是引用所需的空间或“加载因子”,而是创建对象所需的开销。如果密钥是基本类型,或者是2个或更多基元或引用值的组合,则每个密钥都需要自己的对象,其中包含8个字节的开销。
(b)根据我的经验,您通常需要将密钥作为值的一部分(例如,存储客户记录,按客户ID索引,您仍然希望客户ID作为Customer对象的一部分)。这意味着IMO有点浪费,HashMap单独存储对键和值的引用。
注意事项:
用于HashMap键的最常见类型是String。对象创建开销不适用于此处,因此差异会更小。
我有一个2.8的数字,插入到ArrayList中的8880502条目与3148004插入-Xmx256M JVM上的HashMap,但我的ArrayList加载因子是80%而且我的对象非常小--12个字节加8字节对象开销。
我的图和我的实现要求密钥包含在值中,否则我会遇到与对象创建开销相同的问题,它只是HashMap的另一个实现。
< / LI> 醇>我的代码:
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);
}
}
答案 1 :(得分:15)
最简单的方法是查看源代码并以此方式解决问题。但是,你真的在比较苹果和橘子 - 列表和地图在概念上非常不同。您很少根据内存使用情况在它们之间进行选择。
这个问题背后的背景是什么?
答案 2 :(得分:8)
存储在其中的任何一个都是指针。根据您的体系结构,指针应为32或64位(或更多或更少)
10的数组列表往往至少分配10个“指针”(以及一些一次性开销)。
地图必须分配两次(20个指针),因为它一次存储两个值。然后,最重要的是,它必须存储“哈希”。它应该大于地图,在75%的负载下它应该是大约13个32位值(散列)。
所以如果你想要一个随便的答案,比率应该是大约1:3.25左右,但你只是在谈论指针存储 - 非常小,除非你存储大量的对象 - 如果是这样,实用程序能够立即引用(HashMap)和迭代(数组)应该比内存大小更重要。
哦,还有: 数组可以适合您的集合的确切大小。如果您指定大小,HashMaps也可以,但如果它“超出”大小,它将重新分配更大的数组而不使用其中的一些,所以也可能有一些浪费。
答案 3 :(得分:7)
我也没有给你一个答案,但快速谷歌搜索在Java中发现了一个可能有用的功能。
调用Runtime.getRuntime()freeMemory();
所以我建议用相同的数据填充HashMap和ArrayList。记录空闲内存,删除第一个对象,记录内存,删除第二个对象,记录内存,计算差异,...,利润!
你应该用大量的数据来做这件事。即从1000开始,然后是10000,100000,1000000。
编辑:已更正,感谢amischiefr。
编辑: 很抱歉编辑你的帖子,但是如果你打算使用它,这是非常重要的(这对评论来说有点多) 。 freeMemory不会像你想象的那样工作。首先,垃圾收集改变了它的价值。其次,当java分配更多内存时,它的值会发生变化。仅仅使用freeMemory调用并不能提供有用的数据。
试试这个:
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()));
}
或者您可以返回使用的内存并将其存储,然后将其与以后的值进行比较。无论哪种方式,记住2 gcs并从totalMemory()中减去。
再次,抱歉编辑你的帖子!
答案 4 :(得分:3)
Hashmaps尝试维护加载因子(通常为75%已满),您可以将hashmap视为稀疏填充的数组列表。直接比较大小的问题是地图的这个加载因子增长以满足数据的大小。另一方面,ArrayList通过将其内部数组大小加倍来增长以满足其需求。对于相对较小的大小,它们是可比较的,但是当您将越来越多的数据打包到地图中时,它需要大量空引用才能保持散列性能。
在任何一种情况下,我都建议在开始添加之前启动预期的数据大小。这将为实现提供更好的初始设置,并且在两种情况下都可能消耗更少。
<强>更新强>
根据您更新的问题结帐Glazed lists。这是一些由Google的一些人编写的简洁工具,用于执行与您描述的操作类似的操作。它也很快。允许群集,过滤,搜索等。
答案 5 :(得分:3)
HashMap 持有对该值的引用和对该键的引用。
ArrayList 只需保存对该值的引用。
因此,假设密钥使用相同的内存值,HashMap使用的内存增加了50%(尽管严格来说,不是使用该内存的HashMap,因为它只是保留了对它的引用)
另一方面,HashMap为基本操作(get和put)提供常量性能因此,虽然它可能使用更多内存,但使用HashMap获取元素可能比使用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
答案 6 :(得分:2)
基本上,你应该使用“正确的工具”。由于存在不同的实例,您需要一个键/值对(您可以使用HashMap
)和不同的实例,您只需要一个值列表(您可以使用ArrayList
然后,在我看来,“哪一个使用更多记忆”的问题没有实际意义,因为它不是考虑选择一个而不是另一个。
但是要回答这个问题,因为HashMap
存储键/值对而ArrayList
只存储值,我会假设单独向HashMap添加键意味着它会占用更多内存当然,假设我们将它们用相同的值类型进行比较(例如,两者中的值都是字符串)。
答案 7 :(得分:2)
我认为这里有一个错误的问题。
如果您希望提高搜索包含六百万个条目的List
中对象的速度,那么您应该研究这些数据类型的检索操作执行的速度
像往常一样,这些类的Javadoc很清楚地说明了它们提供的性能类型:
此实现为基本操作(get和put)提供了恒定时间性能,假设散列函数在桶之间正确地分散元素。
这意味着HashMap.get(key)为O(1)
。
size,isEmpty,get,set,iterator和listIterator操作以恒定时间运行。添加操作以分摊的常量时间运行,即添加n个元素需要O(n)时间。所有其他操作都以线性时间运行(粗略地说)。
这意味着大多数ArrayList
的操作都是O(1)
,但可能不是您用来查找与特定值匹配的对象的操作。
如果要迭代ArrayList
中的每个元素并测试相等性,或者使用contains()
,那么这意味着您的操作在O(n)
时间(或更差)运行。
如果您不熟悉O(1)
或O(n)
符号,则表示操作需要多长时间。在这种情况下,如果您可以获得恒定时间性能,则需要采用它。如果HashMap.get()
为O(1)
,则表示检索操作的时间大致相同,无论是否有多少条目在Map中。
ArrayList.contains()
之类的事实是O(n)
,这意味着随着列表大小的增长,所需的时间会增加;因此,通过一个包含六百万条目的ArrayList
进行迭代将不会非常有效。
答案 8 :(得分:1)
我不知道确切的数字,但HashMaps更重。比较这两者,ArrayList的内部表示是不言而喻的,但HashMaps保留了Entry对象(Entry),这可以增加你的内存消耗。
它不是那么大,但它更大。一个可视化的好方法是使用动态分析器,例如YourKit,它允许您查看所有堆分配。这很不错。
答案 9 :(得分:1)
This post提供了大量有关Java中对象大小的信息。
答案 10 :(得分:0)
正如Jon Skeet所说,这些是完全不同的结构。地图(例如HashMap)是从一个值到另一个值的映射 - 即,您有一个映射到值的键,在Key-&gt; Value类型的关系中。键是经过哈希处理的,并且放在一个数组中以便快速查找。
另一方面,List是具有顺序的元素的集合--ArrayList碰巧使用数组作为后端存储机制,但这是无关紧要的。每个索引元素都是列表中的单个元素。编辑:根据您的评论,我添加了以下信息:
密钥存储在hashmap中。这是因为不保证散列对于任何两个不同的元素是唯一的。因此,必须在散列冲突的情况下存储密钥。如果您只是想查看一组元素中是否存在元素,请使用Set(此标准实现为HashSet)。如果订单很重要,但您需要快速查找,请使用LinkedHashSet,因为它保持元素的插入顺序。两者的查找时间均为O(1),但LinkedHashSet的插入时间稍长。仅当您实际从一个值映射到另一个值时才使用Map - 如果您只有一组唯一对象,请使用Set,如果您有已排序的对象,请使用List。
答案 11 :(得分:0)
如果您正在考虑两个ArrayLists与一个Hashmap,那么它是不确定的;两者都是部分完整的数据结构。如果你比较Vector和Hashtable,Vector可能更有效,因为它只分配它使用的空间,而Hashtables则分配更多的空间。
如果你需要一个键值对,并且没有做出令人难以置信的内存需求,只需使用Hashmap。
答案 12 :(得分:0)
此site列出了几种常用(并非常见)使用的数据结构的内存消耗。从那里可以看出HashMap
大约是ArrayList
空间的5倍。地图还将为每个条目分配一个额外的对象。
如果您需要可预测的迭代顺序并使用LinkedHashMap
,则内存消耗将更高。
您可以使用Memory Measurer进行自己的记忆测量。
但有两个重要事实要注意:
ArrayList
和HashMap
)确实为空间分配了比当前需要更多的空间,因为否则它们必须经常执行昂贵的调整大小操作。因此,每个元素的内存消耗取决于集合中有多少元素。例如,具有默认设置的ArrayList
对0到10个元素使用相同的内存。