繁体   English   中英

Java HashMap的内存开销与ArrayList相比

[英]Memory overhead of Java HashMap compared to ArrayList

我想知道java HashMap与ArrayList相比的内存开销是多少?

更新:

我想提高搜索大包(600万+)相同对象的特定值的速度。

因此,我正在考虑使用一个或多个HashMap而不是使用ArrayList。 但我想知道HashMap的开销是多少。

据我所知,密钥不是存储的,只是密钥的散列,所以它应该像对象的散列大小+一个指针

但是使用了什么哈希函数? 是Object提供的还是另一个?

如果您将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单独存储对键和值的引用。

注意事项:

  1. 用于HashMap键的最常见类型是String。 对象创建开销不适用于此处,因此差异会更小。

  2. 我有一个2.8的数字,插入到ArrayList中的8880502条目与3148004插入-Xmx256M JVM上的HashMap,但是我的ArrayList加载因子是80%而且我的对象非常小--12个字节加上8个字节的对象开销。

  3. 我的图和我的实现要求密钥包含在值中,否则我会遇到与对象创建开销相同的问题,它只是HashMap的另一个实现。

我的代码:

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)vs iterate(数组)应该比内存大小更重要。

哦,还有:阵列可以适合您收藏的确切尺寸。 如果你指定大小,HashMaps也可以,但如果它“超出”那个大小,它将重新分配一个更大的数组而不使用它的一些,所以也可能有一些浪费。

我也没有给你一个答案,但快速谷歌搜索在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()中减去。

再次,抱歉编辑你的帖子!

Hashmaps尝试维护加载因子(通常为75%已满),您可以将hashmap视为稀疏填充的数组列表。 直接比较大小的问题是地图的这个加载因子增长以满足数据的大小。 另一方面,ArrayList通过将其内部数组大小加倍来增长以满足其需求。 对于相对较小的大小,它们是可比较的,但是当您将越来越多的数据打包到地图中时,它需要大量空引用以保持散列性能。

在任何一种情况下,我建议在开始添加之前启动数据的预期大小。 这将为实现提供更好的初始设置,并且在两种情况下都可能消耗更少。

更新:

根据您更新的问题,查看Glazed列表 这是一些由Google的一些人编写的简洁工具,用于执行与您描述的操作类似的操作。 它也很快。 允许群集,过滤,搜索等

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

基本上,你应该使用“正确的工具”。 由于有不同的实例,您需要一个键/值对(您可以使用HashMap )和不同的实例,您只需要一个值列表(您可以使用ArrayList ),然后问题是“哪个一个人使用更多的记忆“,在我看来,是没有实际意义的,因为它不是考虑选择一个而不是另一个。

但回答这个问题,由于HashMap存储键/值对,而ArrayList只存储值,我认为单独向HashMap添加键意味着它会占用更多内存,当然,假设我们通过它们来比较它们相同的值类型 (例如,两者中的值都是字符串)。

我认为这里有一个错误的问题。

如果你想改善你可以搜索一个物体的速度List包含六个万个条目,那么你应该看看这些数据类型的检索操作的速度有多快执行。

像往常一样,这些类的Javadoc很清楚地表明了它们提供的性能类型:

HashMap

假设散列函数在桶之间正确地分散元素,该实现为基本操作(get和put)提供了恒定时间性能。

这意味着HashMap.get(key)是O(1)

ArrayList

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进行迭代将不会非常有效。

我不知道确切的数字,但HashMaps更重。 比较这两者,ArrayList的内部表示是不言而喻的,但HashMaps保留了Entry对象(Entry),这可以增加你的内存消耗。

它不是那么大,但它更大。 一个可视化的好方法是使用动态分析器,例如YourKit ,它允许您查看所有堆分配。 这很不错。

这篇文章提供了很多关于Java中对象大小的信息。

正如Jon Skeet所说,这些是完全不同的结构。 地图(例如HashMap)是从一个值到另一个值的映射 - 即,您有一个映射到值的键,在Key-> Value类型的关系中。 密钥是经过哈希处理的,并且放在一个数组中以便快速查找。

另一方面,List是具有顺序的元素的集合--ArrayList碰巧使用数组作为后端存储机制,但这是无关紧要的。 每个索引元素都是列表中的单个元素。

编辑:根据您的评论,我添加了以下信息:

密钥存储在hashmap中。 这是因为不保证散列对于任何两个不同的元素是唯一的。 因此,必须在散列冲突的情况下存储密钥。 如果您只是想查看一组元素中是否存在元素,请使用Set(此标准实现为HashSet)。 如果订单很重要,但您需要快速查找,请使用LinkedHashSet,因为它保持元素的插入顺序。 两者的查找时间均为O(1),但LinkedHashSet的插入时间稍长。 仅当您实际从一个值映射到另一个值时才使用Map - 如果您只有一组唯一对象,则使用Set,如果您有已排序的对象,则使用List。

如果您正在考虑两个ArrayLists与一个Hashmap,那么它是不确定的; 两者都是部分完整的数据结构。 如果你比较Vector和Hashtable,Vector可能更有效,因为它只分配它使用的空间,而Hashtables分配更多的空间。

如果你需要一个键值对并且没有做出令人难以置信的内存需求,那么只需使用Hashmap即可。

站点列出了几种常用(并非常见)使用的数据结构的内存消耗。 从那里可以看出HashMap大约是ArrayList空间的5倍。 地图还将为每个条目分配一个额外的对象。

如果您需要可预测的迭代顺序并使用LinkedHashMap ,则内存消耗将更高。

您可以使用Memory Measurer进行自己的内存测量。

但是有两个重要的事实需要注意:

  1. 许多数据结构(包括ArrayListHashMap )确实为空间分配了比当前需要更多的空间,因为否则它们必须经常执行昂贵的调整大小操作。 因此,每个元素的内存消耗取决于集合中有多少元素。 例如,具有默认设置的ArrayList对0到10个元素使用相同的内存。
  2. 正如其他人所说的那样,地图的键也被存储起来。 因此,如果它们不在内存中,您也必须添加此内存成本。 另一个对象通常只需要8个字节的开销,加上其字段的内存,可能还有一些填充。 所以这也将是很多记忆。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM