Memory overhead of Java HashMap compared to ArrayList

后端 未结 13 1765
不知归路
不知归路 2020-11-30 02:15

I am wondering what is the memory overhead of java HashMap compared to ArrayList?

Update:

I would like to improve the speed for searching fo

相关标签:
13条回答
  • 2020-11-30 02:17

    This post is giving a lot of information about objects sizes in Java.

    0 讨论(0)
  • 2020-11-30 02:19

    All that is stored in either is pointers. Depending on your architecture a pointer should be 32 or 64 bits (or more or less)

    An array list of 10 tends to allocate 10 "Pointers" at a minimum (and also some one-time overhead stuff).

    A map has to allocate twice that (20 pointers) because it stores two values at a time. Then on top of that, it has to store the "Hash". which should be bigger than the map, at a loading of 75% it SHOULD be around 13 32-bit values (hashes).

    so if you want an offhand answer, the ratio should be about 1:3.25 or so, but you are only talking pointer storage--very small unless you are storing a massive number of objects--and if so, the utility of being able to reference instantly (HashMap) vs iterate (array) should be MUCH more significant than the memory size.

    Oh, also: Arrays can be fit to the exact size of your collection. HashMaps can as well if you specify the size, but if it "Grows" beyond that size, it will re-allocate a larger array and not use some of it, so there can be a little waste there as well.

    0 讨论(0)
  • 2020-11-30 02:23

    Hashmaps try to maintain a load factor (usually 75% full), you can think of a hashmap as a sparsely filled array list. The problem in a straight up comparison in size is this load factor of the map grows to meet the size of the data. ArrayList on the other hand grows to meet it's need by doubling it's internal array size. For relatively small sizes they are comparable, however as you pack more and more data into the map it requires a lot of empty references in order to maintain the hash performance.

    In either case I recommend priming the expected size of the data before you start adding. This will give the implementations a better initial setting and will likely consume less over all in both cases.

    Update:

    based on your updated problem check out Glazed lists. This is a neat little tool written by some of the Google people for doing operations similar to the one you describe. It's also very quick. Allows clustering, filtering, searching, etc.

    0 讨论(0)
  • 2020-11-30 02:23

    As Jon Skeet noted, these are completely different structures. A map (such as HashMap) is a mapping from one value to another - i.e. you have a key that maps to a value, in a Key->Value kind of relationship. The key is hashed, and is placed in an array for quick lookup.

    A List, on the other hand, is a collection of elements with order - ArrayList happens to use an array as the back end storage mechanism, but that is irrelevant. Each indexed element is a single element in the list.

    edit: based on your comment, I have added the following information:

    The key is stored in a hashmap. This is because a hash is not guaranteed to be unique for any two different elements. Thus, the key has to be stored in the case of hashing collisions. If you simply want to see if an element exists in a set of elements, use a Set (the standard implementation of this being HashSet). If the order matters, but you need a quick lookup, use a LinkedHashSet, as it keeps the order the elements were inserted. The lookup time is O(1) on both, but the insertion time is slightly longer on a LinkedHashSet. Use a Map only if you are actually mapping from one value to another - if you simply have a set of unique objects, use a Set, if you have ordered objects, use a List.

    0 讨论(0)
  • 2020-11-30 02:25

    If you're comparing HashMap with ArrayList, I presume you're doing some sort of searching/indexing of the ArrayList, such as binary search or custom hash table...? Because a .get(key) thru 6 million entries would be infeasible using a linear search.

    Using that assumption, I've done some empirical tests and come up with the conclusion that "You can store 2.5 times as many small objects in the same amount of RAM if you use ArrayList with binary search or custom hash map implementation, versus HashMap". My test was based on small objects containing only 3 fields, of which one is the key, and the key is an integer. I used a 32bit jdk 1.6. See below for caveats on this figure of "2.5".

    The key things to note are:

    (a) it's not the space required for references or "load factor" that kills you, but rather the overhead required for object creation. If the key is a primitive type, or a combination of 2 or more primitive or reference values, then each key will require its own object, which carries an overhead of 8 bytes.

    (b) In my experience you usually need the key as part of the value, (e.g. to store customer records, indexed by customer id, you still want the customer id as part of the Customer object). This means it is IMO somewhat wasteful that a HashMap separately stores references to keys and values.

    Caveats:

    1. The most common type used for HashMap keys is String. The object creation overhead doesn't apply here so the difference would be less.

    2. I got a figure of 2.8, being 8880502 entries inserted into the ArrayList compared with 3148004 into the HashMap on -Xmx256M JVM, but my ArrayList load factor was 80% and my objects were quite small - 12 bytes plus 8 byte object overhead.

    3. My figure, and my implementation, requires that the key is contained within the value, otherwise I'd have the same problem with object creation overhead and it would be just another implementation of HashMap.

    My code:

    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);
        }
    }
    
    0 讨论(0)
  • 2020-11-30 02:26

    HashMap hold a reference to the value and a reference to the key.

    ArrayList just hold a reference to the value.

    So, assuming that the key uses the same memory of the value, HashMap uses 50% more memory ( although strictly speaking , is not the HashMap who uses that memory because it just keep a reference to it )

    In the other hand HashMap provides constant-time performance for the basic operations (get and put) So, although it may use more memory, getting an element may be much faster using a HashMap than a ArrayList.

    So, the next thing you should do is not to care about who uses more memory but what are they good for.

    Using the correct data structure for your program saves more CPU/memory than how the library is implemented underneath.

    EDIT

    After Grant Welch answer I decided to measure for 2,000,000 integers.

    Here's the source code

    This is the output

    $
    $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
    
    0 讨论(0)
提交回复
热议问题