[Source code] HashMap source code analysis, hashmap source code analysis

Source: Internet
Author: User

[Source code] HashMap source code analysis, hashmap source code analysis

// Configure //-----------------------------------------------------------------------------------

Reprint need to indicate the source: http://blog.csdn.net/chdjj

// Configure //-----------------------------------------------------------------------------------

Note: The following source code is based on jdk1.7.0 _ 11



Previous articles introduced some common classes in List sets, such as ArrayList, sorted List, and Vector. This article introduces the Map set, another part of the set framework. This topic describes HashMap. First, review the hash table. Hash table definition: Based on the set hash function and the method for handling conflicts (Open addressing, public overflow, link address, and re-hash ...) map a set of keywords to a finite continuous address set (bucket array or bucket array), and use the "image" of the keywords in the address set as the storage location of the records in the table, this type of table is called a hash table.The ing process is called a hash, and the obtained storage location is called a hash address or a hash address. The hash table has good search performance. When the probability of conflict is small, the time complexity is O (1 ). Load factor: Loadfactor = number of records entered in the table/length of the hash table. Therefore, loadfactor indicates the filling degree of the hash table.Intuitively, the smaller the load factor, the smaller the probability of conflict (because the bucket does not have a few data, the expansion is required), that is, the better the search performance, but at the same time, the space wasted will become larger. On the contrary, the larger the load factor, the higher the probability of conflict (the expansion can only be completed when the bucket is quickly filled up. For example, if the linked list method is used to handle conflicts, in this case, the linked list will be too long ), the worse the query performance, the less space it will waste. We will see later, The default load factor of HashMap is 0.75.
Below, the analysis is still from top to bottom, First, let's look at the Map interface.:
Public interface Map <K, V> {// top-level interface Definition of the Map set // Query Operations int size (); boolean isEmpty (); boolean containsKey (Object key ); // whether it contains the specified key boolean containsValue (Object value); // whether it contains the specified value V get (Object key); // Modification Operations V put (K key, V value ); V remove (Object key); // Bulk Operations void putAll (Map <? Extends K ,? Extends V> m); // batch place element void clear (); // Views // three sets <K> keySet (); // obtain the keyset Collection <V> values (); // obtain the value Set <Map. entry <K, V> entrySet (); // obtain the interface Entry of the key-value set <K, V> {// Internal interface of Map, key value Pair K getKey (); // obtain the key V getValue (); // obtain the value V setValue (V value ); // set the value boolean equals (Object o); int hashCode ();} // Comparison and hashing boolean equals (Object o); int hashCode ();}

The Map interface defines the Operation Specifications of the Map set. The specific implementation is completed by the Implementation class. It has an interface Entry, representing a key-value pair.
AbstractMap is an abstract class that implements most functions in the Map interface.
public abstract class AbstractMap<K,V> implements Map<K,V>

Here are several methods:

Public boolean containsKey (Object key) {Iterator <Map. entry <K, V> I = entrySet (). iterator (); // obtain the iterator if (key = null) {// judge whether the key is null and process while (I. hasNext () {Entry <K, V> e = I. next (); if (e. getKey () = null) // if it is null, use the equal sign to judge return true;} else {while (I. hasNext () {Entry <K, V> e = I. next (); if (key. equals (e. getKey () // if it is not empty, use the equals method to determine return true;} return false;} public boolean containsValue (Object value) {Iterator <Entry <K, v> I = entrySet (). iterator (); if (value = null) {while (I. hasNext () {Entry <K, V> e = I. next (); if (e. getValue () = null) return true;} else {while (I. hasNext () {Entry <K, V> e = I. next (); if (value. equals (e. getValue () return true;} return false ;}
The first method is the containsKey and containsValue methods. Note that the key and value here are allowed to be empty, that is, the subclass of this method supports the null key and value by default. The entrySet method here is an abstract method:
public abstract Set<Entry<K,V>> entrySet();

AbstractMap does not implement the put method, but simply throws an exception, which requires the subclass to rewrite this method:

 public V put(K key, V value) {        throw new UnsupportedOperationException();    }

But it implements the get method:
Public V get (Object key) {Iterator <Entry <K, V> I = entrySet (). iterator (); if (key = null) {// still perform different processing based on whether the key value is null. hasNext () {Entry <K, V> e = I. next (); if (e. getKey () = null) return e. getValue () ;}} else {while (I. hasNext () {Entry <K, V> e = I. next (); if (key. equals (e. getKey () return e. getValue () ;}} return null ;}

Next we will look at HashMap.

public class HashMap<K,V>    extends AbstractMap<K,V>    implements Map<K,V>, Cloneable, Serializable

HashMap inherits the AbstractMap class and implements the Map interface, Cloneable, and Serializable interfaces. The member variables are as follows:
Static final int DEFAULT_INITIAL_CAPACITY = 16; // default initial capacity static final int MAXIMUM_CAPACITY = 1 <30; // static final float DEFAULT_LOAD_FACTOR = 0.75f; // The default load factor is 0.75 transient Entry <K, V> [] table; // The bucket array, which stores the key-Value Pair transient int size; // The number of actually stored key-value pairs int threshold; // HashMap threshold, used to determine whether to adjust the capacity of HashMap (threshold = capacity * loading Factor) final float loadFactor; // loading Factor transient int modCount; // Number of hashmap changes static final int ALTERNATIVE_HASHING_THRESHOLD_DEFAULT = Integer. MAX_VALUE;

Here we get the following information: 1. the default size of HashMap is 16, that is, the default length of the bucket array is 16; 2. the default load factor of HashMap is 0.75; 3. the bucket array in HashMap stores the Entry object, that is, the key-Value Pair object.Let's look at the constructor again:
Public HashMap (int initialCapacity, float loadFactor) {// You can manually set the initial capacity and load factor constructor if (initialCapacity <0) throw new IllegalArgumentException ("Illegal initial capacity: "+ initialCapacity); if (initialCapacity> MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; if (loadFactor <= 0 | Float. isNaN (loadFactor) throw new IllegalArgumentException ("Illegal load factor:" + loadFactor); // Find a power 2> = initialCapacity // find the power int capacity = 1 for the smallest 2 greater than initialCapacity; while (capacity <initialCapacity) capacity <= 1; this. loadFactor = loadFactor; threshold = (int) Math. min (capacity * loadFactor, MAXIMUM_CAPACITY + 1); // initialize the bucket array table = new Entry [capacity]; useAltHashing = sun. misc. VM. isBooted () & (capacity> = Holder. ALTERNATIVE_HASHING_THRESHOLD); init (); // a hook function, which is empty by default} public HashMap (int InitialCapacity) {this (initialCapacity, DEFAULT_LOAD_FACTOR);} public HashMap () {// use the default initial capacity and default loading factor to construct HashMap this (DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR );} public HashMap (Map <? Extends K ,? Extends V> m) {this (Math. max (int) (m. size ()/DEFAULT_LOAD_FACTOR) + 1, DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR); putAllForCreate (m);} // internal utilities void init (){}

Notes: 1. the constructor supports specifying the initial capacity and loading factor. To avoid performance problems caused by array resizing, we recommend that you specify the initial capacity as needed. Do not modify the load factor as much as possible. 0.75 is a reliable value. 2. the actual capacity is generally greater than the initialCapacity we passed in, because an integer power greater than initialCapacity and two is found as the actual capacity through a loop. Unless the passed number is exactly the integer power of 2 (capacity takes the integer power of 2 to make the collision probability of different hash values smaller, in this way, the elements are evenly hashed in the hash table .).
Through the above analysis, we know how to implement this Entry when HashMap stores key-value pairs through the Entry array? Next let's take a look. Entry implementation:
Static class Entry <K, V> implements Map. entry <K, V> {// implements Map. entry interface final K key; // key, final type, unchangeable/V value; // value Entry <K, V> next; // HashMap solves the conflict through the linked list method, next points to the next element of the linked list, int hash; // hash value, *** Creates new entry. * /// the constructor must specify the next node of the linked list. All conflicting nodes are placed on the same Linked List Entry (int h, K k, V v, Entry <K, V> n) {value = v; next = n; key = k; hash = h;} public final K getKey () {return key;} public final V getValue () {return value;} // Allow value public final V setValue (V newValue) {V oldValue = value; value = newValue; return oldValue;} public final boolean equals (Object o) {if (! (O instanceof Map. entry) return false; Map. entry e = (Map. entry) o; // ensure that all key values are equal. Object k1 = getKey (); Object k2 = e. getKey (); if (k1 = k2 | (k1! = Null & k1.equals (k2) {Object v1 = getValue (); Object v2 = e. getValue (); if (v1 = v2 | (v1! = Null & v1.equals (v2) return true;} return false;} public final int hashCode () {// if the key is null, the hash value is 0, otherwise, return (key = null? 0: key. hashCode () ^ (value = null? 0: value. hashCode ();} public final String toString () {return getKey () + "=" + getValue ();} /*** This method is invoked whenever the value in an entry is * overwritten by an invocation of put (k, v) for a key k that's already * in the HashMap. */void recordAccess (HashMap <K, V> m) {}/ *** This method is invoked whenever the entry is * removed from the table. */void recordRemoval (HashMap <K, V> m ){}}

Notes: 1. The HashMap internal array stores key-value pairs, that is, Entry objects; 2. The Entry object stores keys and values, and holds a next pointer pointing to the next Entry object (HashMap uses the linked list method to resolve conflicts ); 3. The Entry can be set through setValue, but the key cannot be set.
Next we will study the more important methods in HashMap. Start with put:
Public V put (K key, V value) {// Add a key-value pair to the set if (key = null) // if the key is null, call putForNullKey return putForNullKey (value); int hash = hash (key); // otherwise, a hash index value int I = indexFor (hash, table. length); // locate the insert position based on the index value // cyclically traverse the Entry linked list at the specified position. If a key is found to be exactly the same as the current key, overwrite the value corresponding to the original key and return the original value for (Entry <K, V> e = table [I]; e! = Null; e = e. next) {Object k; // The hash value is the same and the key is the same if (e. hash = hash & (k = e. key) = key | key. equals (k) {V oldValue = e. value; e. value = value; // replace the original value e. recordAccess (this); return oldValue ;}// if such a key is not found, insert the current key value to this position and place it in the head of the linked list. modCount ++; addEntry (hash, key, value, I); return null ;}

The put method adds a key-value pair to hashMap. Note the following when using this method: 1. The allowed key is null. The put method processes the null key and calls the pullforNullKey method:
Private V putForNullKey (V value) {// null key, whose hash value is 0, must be stored in the zero index position of the array. // We Need to traverse the zero-position entry linked list. If there is already a null key, it overwrites for (Entry <K, V> e = table [0]; e! = Null; e = e. next) {if (e. key = null) {V oldValue = e. value; e. value = value; e. recordAccess (this); return oldValue ;}// if not, insert modCount ++; addEntry (0, null, value, 0); return null ;}

2. Two keys are not allowed to be the same. If the keys are the same, the values of the keys inserted later will overwrite the previous values. 3. HashMap obtains the hash value of the key by calling the hash () method, and finds the actual Insertion Location through the indexFor method. The Code is as follows:
Final int hash (Object k) {// generate the hash value int h = 0 based on the key; if (useAltHashing) {if (k instanceof String) {return sun. misc. hashing. stringHash32 (String) k);} h = hashSeed;} h ^ = k. hashCode (); h ^ = (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4);} // calculate the position of the key in the bucket Array Based on the hash value static int indexFor (int h, int length) {return h & (length-1); // according to the put method, this length is the length of the array, and the constructor finds that the length of the array is always the integer power of 2, this & operation is actually an efficient representation of h % length, which can make the result smaller than the array length .}

4. The put method uses the addEntry method to insert the key value to an appropriate position: 5. When the capacity exceeds the threshold value (threshold), it will be resized. The expanded array is twice the original array.
Void addEntry (int hash, K key, V value, int bucketIndex) {if (size> = threshold) & (null! = Table [bucketIndex]) {// The capacity exceeds the resize threshold (2 * table. length); // The array is resized to twice the original hash = (null! = Key )? Hash (key): 0; bucketIndex = indexFor (hash, table. length);} createEntry (hash, key, value, bucketIndex);} void createEntry (int hash, K key, V value, int bucketIndex) {Entry <K, v> e = table [bucketIndex]; // obtain the original Entry object table [bucketIndex] = new Entry <> (hash, key, value, e) at this position ); // Insert the current key value to this position and act as the Start Node of the linked list. Its next Pointer Points to the previous Entry size ++ ;}

The resize method is the expansion method:
Void resize (int newCapacity) {Entry [] oldTable = table; int oldCapacity = oldTable. length; if (oldCapacity = MAXIMUM_CAPACITY) {threshold = Integer. MAX_VALUE; return;} Entry [] newTable = new Entry [newCapacity]; // create a new array boolean oldAltHashing = useAltHashing; useAltHashing | = sun. misc. VM. isBooted () & (newCapacity> = Holder. ALTERNATIVE_HASHING_THRESHOLD); boolean rehash = oldAltHashing ^ useAltHas Hing; transfer (newTable, rehash); // transfers all key-value pairs in the original array to the new array table = newTable; threshold = (int) Math. min (newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);} void transfer (Entry [] newTable, boolean rehash) {int newCapacity = newTable. length; for (Entry <K, V> e: table) {// You Need to traverse each Entry, time consumed while (null! = E) {Entry <K, V> next = e. next; if (rehash) {e. hash = null = e. key? 0: hash (e. key);} int I = indexFor (e. hash, newCapacity); e. next = newTable [I]; newTable [I] = e; e = next ;}}}

6. The expansion operation needs to open up a new array and re-hash all key-value pairs in the original array, which is very time-consuming. We should try to avoid HashMap resizing.
Let's take a look at the get method:
Public V get (Object key) {if (key = null) // if the key is empty, return getForNullKey (); Entry <K, V> entry = getEntry (key ); // obtain the Entry object // if it is not found, null is returned. Otherwise, return null = entry? Null: entry. getValue ();}

The getForNullKey method is to search for the null key in the linked list of the array 0 index bit:
 private V getForNullKey() {        for (Entry<K,V> e = table[0]; e != null; e = e.next) {            if (e.key == null)                return e.value;        }        return null;    }

The getEntry method generates the hash value through the key, obtains its index position in the array, searches for the linked list at the position, finds the First Matching key, and returns the Entry object:

final Entry<K,V> getEntry(Object key) {        int hash = (key == null) ? 0 : hash(key);        for (Entry<K,V> e = table[indexFor(hash, table.length)];             e != null;             e = e.next) {            Object k;            if (e.hash == hash &&                ((k = e.key) == key || (key != null && key.equals(k))))                return e;        }        return null;    }

Let's look at the remove Method again:
Public V remove (Object key) {Entry <K, V> e = removeEntryForKey (key); return (e = null? Null: e. value);} final Entry <K, V> removeEntryForKey (Object key) {int hash = (key = null )? 0: hash (key); // calculate the hash value int I = indexFor (hash, table. length); // locate the index position Entry <K, V> prev = table [I]; Entry <K, V> e = prev; while (e! = Null) {// locate the key in the traversal table, and modify the linked list pointer to Entry <K, V> next = e. next; Object k; if (e. hash = hash & (k = e. key) = key | (key! = Null & key. equals (k) {modCount ++; size --; if (prev = e) table [I] = next; else prev. next = next; e. recordRemoval (this); return e;} prev = e; e = next;} return e ;}

Finally, let's look at the clear method:
Public void clear () {modCount ++; Entry [] tab = table; for (int I = 0; I <tab. length; I ++) // traverse the array tab [I] = null; // empty size = 0 ;}

Summary: 1. The default HashMap size is 16, that is, the default length of the bucket array is 16; 2. The default load factor of HashMap is 0.75; 3. the bucket array in HashMap stores the Entry object, that is, the key-Value Pair object.
4. the constructor supports specifying the initial capacity and loading factor. To avoid performance problems caused by array resizing, we recommend that you specify the initial capacity as needed. Do not modify the load factor as much as possible. 0.75 is a reliable value. 5. The length of the bucket array is always the integer power of 2 (greater than or equal to the specified initial capacity). This reduces the conflict probability and improves the search efficiency. (We can see from the indexfor function that h & (length-1), if length is an odd number and length-1 is an even number, then h & (length-1) the last digit of the result must be 0. That is to say, all keys are hashed to the even subscript position of the array, which wastes nearly half of the space. In addition, the integer power of length 2 ensures that h & (length-1) is equivalent to h % length ). 6. HashMap accepts the null key; 7. HashMap does not allow duplicate keys, but the values can be repeated. If the key is repeated, the new value overwrites the old one. 8. HashMap solves conflicts through the linked list method. Each Entry has a next pointer pointing to the next Entry. The conflicting element (not the same key, but the same hash value) forms a linked list. The newly inserted key-value pair is always at the head of the linked list. 9. When the capacity exceeds the threshold value (threshold), expansion will occur. The expanded array is twice the original array. The expansion operation needs to open up new arrays and re-hash all key-value pairs in the original array, which is very time-consuming. We should try to avoid HashMap resizing. 10. HashMap is NOT thread-safe.



In the put Method of HashMap source code, ehash = hash & (k = ekey) = key | keyequals (k ))

If the two objects are the same, their hashCode values must be the same, so e. hash = hash, but this is only a necessary condition, not a sufficient condition. That is to say, if the hashCode of the two objects is the same, they are not necessarily the same object, so the key is required later. equals (k)

Java checks the source code of hashmap and finds that the elements are not loaded into entrySet, but can be traversed as follows:

The help document says: return the collection view of the ing relationships contained in this ing. In the returned collection, each element is a Map. Entry.
EntrySet is just a view. Without specific data, you can actually get data from HashMap. For details, see the source code of entry and entrySet to know whether the data actually comes from table.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.