HashMap implementation principle JDK1.8

In JDK1.6, JDK1.7 in, HashMap using the bit bucket list + realization that the use of linked lists conflict, list the same hash value are stored in a linked list. But when many of the elements in a bucket, that is more equal to the hash value of the element, the low efficiency in order to find the key value. In the JDK1.8, using the HashMap bit bucket chain + + red-black tree to achieve, when the chain length exceeds a threshold value (8), the list is converted into a red-black tree , which greatly reduces the search time.

 

 

Simply put under the realization of the principle of HashMap:

First, each element is a list (possibly inaccurate statements) array, when adding an element (key-value), it first calculates the hash value of the element of the key, in order to determine the position of the array is inserted, but there may be elements of the same hash value has been placed in the same position of the array, this time after the element is added to the same hash value, they are in the same position in the array, but the list is formed, hash value on the same each chain is the same, so array is stored in a linked list. When the list is too long, the list is converted to a red-black tree, thereby greatly enhancing the efficiency of the search.

     When the capacity of the array of the list of the initial capacity exceeds 0.75, then the hash chain amplified by 2 array, the array to move the original list in the new array

That HashMap schematic is:

A, JDK1.8 involved in a data structure

1, the bit bucket array

 

 
  1. the Node transient <K, V> [] Table; // store (bit bucket) array </ k, v>  

         2, the array elements Node <K, V> Entry implements the interface

 
Copy the code
 1 // Node is a singly linked list, which implements the interface Map.Entry  
 2 static class Node<k,v> implements Map.Entry<k,v> {  
 3     final int hash;  
 4     final K key;  
 5     V value;  
 6     Node<k,v> next;  
 // constructor for 7 Hash value of a node key  
 8     Node(int hash, K key, V value, Node<k,v> next) {  
 9         this.hash = hash;  
10         this.key = key;  
11         this.value = value;  
12         this.next = next;  
13     }  
14    
15     public final K getKey()        { return key; }  
16     public final V getValue()      { return value; }  
17     public final String toString() { return key + = + value; }  
18    
19     public final int hashCode() {  
20         return Objects.hashCode(key) ^ Objects.hashCode(value);  
21     }  
22    
23     public final V setValue(V newValue) {  
24         V oldValue = value;  
25         value = newValue;  
26         return oldValue;  
27     }  
// node 28 determines whether the two are equal, and if the key value are equal, returns true. You can compare itself to true  
29     public final boolean equals(Object o) {  
30         if (o == this)  
31             return true;  
32         if (o instanceof Map.Entry) {  
33             Map.Entry<!--?,?--> e = (Map.Entry<!--?,?-->)o;  
34             if (Objects.equals(key, e.getKey()) &&  
35                 Objects.equals(value, e.getValue()))  
36                 return true;  
37         }  
38         return false;  
39     }  
Copy the code

3, red-black tree

Copy the code
 1 // red-black tree  
 2 static final class TreeNode<k,v> extends LinkedHashMap.Entry<k,v> {  
 3 TreeNode <k, v> parent; // parent node  
 4 TreeNode <k, v> left; // left subtree  
 5 TreeNode <k, v> right; // right subtree  
 6     TreeNode<k,v> prev;    // needed to unlink next upon deletion  
 7 boolean red; // color attribute  
 8     TreeNode(int hash, K key, V val, Node<k,v> next) {  
 9         super(hash, key, val, next);  
10     }  
11    
12 // Returns the root node of the current node  
13     final TreeNode<k,v> root() {  
14         for (TreeNode<k,v> r = this, p;;) {  
15             if ((p = r.parent) == null)  
16                 return r;  
17             r = p;  
18         }  
19     }  
Copy the code

 

Second, the source of the data field

Load factor (default 0.75): Why do we need to use load factor, why expansion it ? If the ratio is large because a lot of padding, indicating the use of the space, if the expansion has not, then the list will be longer and longer, to find such low efficiency, because a great length of the list (of course, the latest version of the red-black tree after will be much improved), after expansion, a list of the original list will each array is divided into two sub-lists are parity hanging location of the new hash list array, thus reducing the length of each of the list, increasing search efficiency

Originally HashMap is space for time, so much more than filling is not necessary. But filling ratio is too small will lead to wasted space. If the memory is concerned, the filling ratio can be slightly larger, if the primary concern lookup performance, filling ratio may be slightly smaller.

 

Copy the code
 1 public class HashMap<k,v> extends AbstractMap<k,v> implements Map<k,v>, Cloneable, Serializable {  
 2     private static final long serialVersionUID = 362498820763181265L;  
 3     static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16  
 4 static final int MAXIMUM_CAPACITY = 1 << 30; // maximum capacity  
 5 static final float DEFAULT_LOAD_FACTOR = 0.75f; // fill ratio  
 When 6 // add an element to a bit bucket, chain length reaches its 0800 list is converted into a red-black tree  
 7     static final int TREEIFY_THRESHOLD = 8;  
 8     static final int UNTREEIFY_THRESHOLD = 6;  
 9     static final int MIN_TREEIFY_CAPACITY = 64;  
// array element is stored; 10 transient Node <k, v> [] table  
11     transient Set<map.entry<k,v>> entrySet;  
// number of storage elements; 12 transient int size  
13 transient int modCount; // number of modified fast-fail mechanism  
14 int threshold; // the threshold value when the actual size (volume filling ratio *) exceeds a threshold value, the expansion will be   
15 final float loadFactor; // padding (abbreviated ...... later) than  
Copy the code

 

Three, HashMap constructor

 

HashMap configuration there are four ways, are mainly related to the parameter, the specified initial capacity, and is used to initialize the specified filling ratio of Map

 

Copy the code
 1 // constructor 1  
 2 public HashMap(int initialCapacity, float loadFactor) {  
 3 // specified initial capacity nonnegative  
 4     if (initialCapacity < 0)  
 5         throw new IllegalArgumentException(Illegal initial capacity:  +  
 6                                            initialCapacity);  
 7 // if the specified initial capacity greater than the maximum capacity, is set to the maximum capacity  
 8     if (initialCapacity > MAXIMUM_CAPACITY)  
 9         initialCapacity = MAXIMUM_CAPACITY;  
10 // filling ratio is positive  
11     if (loadFactor <= 0 || Float.isNaN(loadFactor))  
12         throw new IllegalArgumentException(Illegal load factor:  +  
13                                            loadFactor);  
14     this.loadFactor = loadFactor;  
15 this.threshold = tableSizeFor (initialCapacity); // new expansion threshold  
16 }  
17    
18 // Constructor 2  
19 public HashMap(int initialCapacity) {  
20     this(initialCapacity, DEFAULT_LOAD_FACTOR);  
21 }  
22    
23 // constructor 3  
24 public HashMap() {  
25     this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted  
26 }  
27    
28 // Constructors 4 initialization elemental m hash map  
29 public HashMap(Map<!--? extends K, ? extends V--> m) {  
30     this.loadFactor = DEFAULT_LOAD_FACTOR;  
31     putMapEntries(m, false);  
32 }  
Copy the code

 

Four, HashMap access mechanism

 

1, HashMap how getValue value, look at the source code

Copy the code
 1 public V get(Object key) {  
 2         Node<K,V> e;  
 3         return (e = getNode(hash(key), key)) == null ? null : e.value;  
 4     }  
 5       /** 
 6      * Implements Map.get and related methods 
 7      * 
 8      * @param hash hash for key 
 9      * @param key the key 
10      * @return the node, or null if none 
11      */  
12     final Node<K,V> getNode(int hash, Object key) {  
13 Node <K, V> [] tab; // Entry object array  
14 Node <K, V> first, e; // through the first tab position in the hash array  
15     int n;  
16     K k;  
17 / * find the first Node inserted, and a hash value is n-1 phase, tab [(n - 1) & hash] * /  
18 // That same hash value on one strand  
19         if ((tab = table) != null && (n = tab.length) > 0 &&(first = tab[(n - 1) & hash]) != null) {  
20 / * check the first Node is not looking Node * /  
21             if (first.hash == hash && // always check first node  
22 ((k = first.key) == key || (key! = Null && key.equals (k)))) // determines that the hash to the same, key values ​​to be the same  
23                 return first;  
24 / * Check behind first node * /  
25             if ((e = first.next) != null) {  
26                 if (first instanceof TreeNode)  
27                     return ((TreeNode<K,V>)first).getTreeNode(hash, key);  
28 / * list traversal later, to find the key value and the hash values ​​are the same the Node * /  
29                 do {  
30                     if (e.hash == hash &&  
31                         ((k = e.key) == key || (key != null && key.equals(k))))  
32                         return e;  
33                 } while ((e = e.next) != null);  
34             }  
35         }  
36         return null;  
37     }  
Copy the code

 

Get GET (key) method when the hash key is calculated hash & (n-1) obtained in the list array position first = tab [hash & (n-1)], first determine first the key is equal to the parameter key, not other then traverse the linked list back to find the same key value is returned to the value corresponding to value

 

2, HashMap how to put (key, value); see source code

 

Copy the code
 1 public V put(K key, V value) {  
 2         return putVal(hash(key), key, value, false, true);  
 3     }  
 4      /** 
 5      * Implements Map.put and related methods 
 6      * 
 7      * @param hash hash for key 
 8      * @param key the key 
 9      * @param value the value to put 
10      * @param onlyIfAbsent if true, don't change existing value 
11      * @param evict if false, the table is in creation mode. 
12      * @return previous value, or null if none 
13      */  
14 final V putVal(int hash, K key, V value, boolean onlyIfAbsent,  
15                    boolean evict) {  
16         Node<K,V>[] tab;   
17     Node<K,V> p;   
18     int n, i;  
19         if ((tab = table) == null || (n = tab.length) == 0)  
20             n = (tab = resize()).length;  
21 / * If the table is (n-1) & hash value is empty, a new node is inserted at the position * /  
22         if ((p = tab[i = (n - 1) & hash]) == null)  
23             tab[i] = newNode(hash, key, value, null);  
24 / * indicates there is a conflict, the conflict begins processing * /  
25         else {  
26             Node<K,V> e;   
27         K k;  
28 / * check the first Node, p is the mean value * /  
29             if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))  
30                 e = p;  
31             else if (p instanceof TreeNode)  
32                 e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);  
33             else {  
34                 for (int binCount = 0; ; ++binCount) {  
35 / * pointer is null hung behind * /  
36                     if ((e = p.next) == null) {  
37                         p.next = newNode(hash, key, value, null);  
38 // nodes if the conflict has reached eight, to see whether a change in the structure of the storage node of the conflict,               
39 // treeifyBin first determines the length of the current hashMap, if less than 64, only  
40 // resize, Table expansion, if it reaches 64, then the storage structure is black tree conflicts  
41                         if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st  
42                             treeifyBin(tab, hash);  
43                         break;  
44                     }  
45 / * If you have the same key value is over traversal * /  
46                     if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))  
47                         break;  
48                     p = e;  
49                 }  
50             }  
51 / * have the same key value is on the list * /  
52 if (e! = Null) {// existing mapping for key, the key is present Value  
53                 V oldValue = e.value;  
54                 if (!onlyIfAbsent || oldValue == null)  
55                     e.value = value;  
56                 afterNodeAccess(e);  
57 return oldValue; // Value returned value occurring  
58             }  
59         }  
60         ++modCount;  
61 / * if the current size is greater than the threshold, the threshold of the initial capacity was originally 0.75 * * /  
62         if (++size > threshold)  
63 resize (); // double expansion  
64         afterNodeInsertion(evict);  
65         return null;  
66     }  
Copy the code

 

Simply add the following key process to put (key, value) of:
1, is determined Tab key array [] Otherwise, if the default size of a resize () is empty, or null, the pair;
2, calculated according to the key hash key It is worth to insertion array index i, if the tab [i] == null, add a new node directly, or transferred. 3
3 array determines the current processing mode is the hash collision list or red-black tree (check node a first type can), were treated

 

Five, HasMap expansion mechanism resize ();

Constructing the hash table, if not specified in the original size, the default size is 16 (i.e. Node array size 16), if the Node [] array element reaches (filling ratio * Node.length) readjusted HashMap size becomes 2 times the original size , expansion is very time-consuming

 

Copy the code
 1  /** 
 2     * Initializes or doubles table size.  If null, allocates in 
 3     * accord with initial capacity target held in field threshold. 
 4     * Otherwise, because we are using power-of-two expansion, the 
 5     * elements from each bin must either stay at same index, or move 
 6     * with a power of two offset in the new table. 
 7     * 
 8     * @return the table 
 9     */  
10    final Node<K,V>[] resize() {  
11        Node<K,V>[] oldTab = table;  
12        int oldCap = (oldTab == null) ? 0 : oldTab.length;  
13        int oldThr = threshold;  
14        int newCap, newThr = 0;  
15       
16 / * If the length of the old table is not empty * /  
17        if (oldCap > 0) {  
18            if (oldCap >= MAXIMUM_CAPACITY) {  
19                threshold = Integer.MAX_VALUE;  
20                return oldTab;  
21            }  
22 / * set the new table length is twice the length of the old table, newCap = 2 * oldCap * /  
23            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&  
24                     oldCap >= DEFAULT_INITIAL_CAPACITY)  
25 / * the doors of the new table the old table limit is set to double the threshold, newThr = oldThr * 2 * /  
26                newThr = oldThr << 1; // double threshold  
27        }  
28 / * If the length of the old table is 0, that is the first table initialization * /  
29        else if (oldThr > 0) // initial capacity was placed in threshold  
30            newCap = oldThr;  
31        else {               // zero initial threshold signifies using defaults  
32            newCap = DEFAULT_INITIAL_CAPACITY;  
33            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);  
34        }  
35       
36       
37       
38        if (newThr == 0) {  
39 float ft = (float) newCap * loadFactor; // new table length is multiplied by load factor  
40            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?  
41                      (int)ft : Integer.MAX_VALUE);  
42        }  
43        threshold = newThr;  
44        @SuppressWarnings({"rawtypes","unchecked"})  
45 / * Start the following new configuration table, the data table initialization * /  
46        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];  
47 table = newTab; // table assigned to the new table  
48 if (oldTab! = Null) {// the data in the original table is not empty original table should move to a new table      
49 / * traversing the original old table * /        
50            for (int j = 0; j < oldCap; ++j) {  
51                Node<K,V> e;  
52                if ((e = oldTab[j]) != null) {  
53                    oldTab[j] = null;  
54 if (e.next == null) // DESCRIPTION This list is not directly on the new node table e.hash & (newCap - 1) positions  
55                        newTab[e.hash & (newCap - 1)] = e;  
56                    else if (e instanceof TreeNode)  
57                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);  
58 / * if there is behind the e chain, represented here with a single-chain e later, you need to traverse a single linked list, each node weight * /  
59 else {// preserve order to ensure that the order  
//// 60 calculates the new position of the new table, and carrying  
61                        Node<K,V> loHead = null, loTail = null;  
62                        Node<K,V> hiHead = null, hiTail = null;  
63                        Node<K,V> next;  
64                       
65                        do {  
66 next = e.next; // record a next node  
67 // new table is twice the capacity of the old table, put on an instance of a single list split into two teams,  
68 //e.hash&oldCap is even a team, e.hash & oldCap odd one pair  
69                            if ((e.hash & oldCap) == 0) {  
70                                if (loTail == null)  
71                                    loHead = e;  
72                                else  
73                                    loTail.next = e;  
74 loTail = e;  
75                            }  
76                            else {  
77                                if (hiTail == null)  
78 hiHead = e;  
79                                else  
80                                    hiTail.next = e;  
81                                hiTail = e;  
82                            }  
83                        } while ((e = next) != null);  
84                       
85 if (loTail! = Null) {// lo team is not null, the original location on the new table  
86                            loTail.next = null;  
87 newtab [j] = loHead;  
88                        }  
89 if (hiTail! = Null) {// hi team is not null, placed in a new table position j + oldCap  
90                            hiTail.next = null;  
91 newtab [j + oldCap] = hiHead;  
92                        }  
93                    }  
94                }  
95            }  
96        }  
97        return newTab;  
98    }  
Copy the code

 


Six, JDK1.8 with improved red-black tree

 

In Java  for HashMap source code for the optimized jdk8, in JDK7, HashMap process "collisions" when the linked list are stored, when the collision node number, the query time is O (n).
In jdk8, the HashMap process "collision" red-black tree adds this data structure, when a small collision nodes, linked list memory, when large (> 8), use of red-black tree (query time characterized by It is O (logn)) stores (a threshold control, larger than the threshold (8), to be converted into red-black tree list storage memory)

problem analysis:

You may also know that the disastrous impact the performance of a hash collision will hashMap. If the value of the plurality of hashCode () falls in the same bucket, these values ​​are stored in a linked list. The worst case, all of the key are mapped to the same bucket, so it devolved into a hashmap list - look from time O (1) to O (n).

 

With the growth of the size of the HashMap overhead get () method is also growing. Since all of the records are in the same bucket long list, a record average query needs to traverse half of the list.

JDK1.8HashMap red-black tree is solved :

         If you record a barrel is too large, then (currently TREEIFY_THRESHOLD = 8), dynamic use of HashMap will be a special treemap realize replace in it. The result of this will be better, it is O (logn), rather than the bad O (n).

        How does it work? KEY corresponding to those of the foregoing recording conflict simply appended to the back of a linked list, which can be recorded by traversing lookup. But after this threshold is exceeded HashMap start list will be upgraded to a binary tree, using the hash value as a branch of the tree variables, if two hash values vary, but point to the same bucket, then the larger one will be inserted into the right subtree in. If the hash values are equal, the HashMap key value desired is to achieve the best Comparable interface, so that it can be inserted in order . This is key for the HashMap is not required, but if realized would be best. If you do not implement this interface, when a serious collision in the hash appears, and you do not expect to get a performance boost.

Original Address: https: //www.cnblogs.com/little-fly/p/7344285.html

Guess you like

Origin www.cnblogs.com/adspark/p/12164992.html