簡體   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