HashMap and principles underlying implementation (source code parsing)

Original link: https://blog.csdn.net/qq_41345773/article/details/92066554

Note: the contents of the article based on JDK1.7 analyze, explain 1.8 make changes at the end of the article.

First, first to familiarize yourself with our common HashMap
1, an overview of
the way HashMap Map-based interface, the storage elements are key-value pairs, and allows the use and construction of a null value null, because the key must be unique, so only one key for the null, HashMap not guarantee the order into further elements, it is disordered, and not sequentially into the same. HashMap is thread safe.

2, the inheritance
public class the HashMap <K, V> the extends AbstractMap <K, V>
    the implements the Map <K, V>, the Cloneable, the Serializable
 
. 3, the basic properties

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4 ; // default initial size of 16 
static DEFAULT_LOAD_FACTOR a float Final = 0.75f; // load factor 0.75
static Final the Entry [] = {} EMPTY_TABLE <,??>; // initialize array default
transient int size; // number of elements in the HashMap
int threshold; // determine whether the need to adjust the capacity of the HashMap  
Note: HashMap expansion operation is a very time consuming task, so if we can estimate Map of capacity, it is best to a default initial value, to avoid multiple expansion. HashMap thread is unsafe, multi-threaded environment is recommended ConcurrentHashMap.

Second, the difference between frequently asked HashMap and Hashtable of
1, thread safety
of both the main difference is that Hashtable is thread safe, while HashMap is not thread safe.

Hashtable implementation methods which are added to the synchronized keyword to ensure thread synchronization, and therefore relatively HashMap performance will be higher, we usually recommend the use of HashMap Unless otherwise required when used in a multithreaded environment if used HashMap need to use Collections. synchronizedMap () method to obtain a thread-safe collection.

Note:

Collections.synchronizedMap () implementation principle is defined inside the class Collections SynchronizedMap. This class implements the Map interface, using synchronized method call to ensure thread synchronization, or we pass HashMap instance of course actually operate, simple He said that Collections.synchronizedMap () method helped us to automatically add the synchronized during operation HashMap to implement thread synchronization, similar to other Collections.synchronizedXX method is similar principle.

2, for different null of
HashMap null can be used as a key, while Hashtable does not allow null as a key
though HashMap support null value as a key, but the proposal is still to avoid such use, because if not used with care, and if therefore cause problems, investigation and it was very cumbersome.
Note: When HashMap null as key, is always stored in a node table on the first array.

3, inheritance structure
HashMap is an implementation of the Map interface, HashTable implements Map interface and the abstract class Dictionary.

4, the initial capacity and expansion of
the initial capacity of the HashMap 16, Hashtable initial capacity of 11, both the fill factor of 0.75 is the default.

When the current capacity is doubled HashMap i.e. expansion: capacity * 2, when a double capacity expansion Hashtable i.e. +1: capacity * 2 + 1.

5, two different methods of calculating hash
Hashtable hash calculated hashcode key is used as the length of the array table directly modulo

key.hashCode hash = int ();
int index = (hash & 0x7FFFFFFF) tab.length%;
the HashMap hash calculated on the key hashcode conducted a second hash, the hash value to obtain a better and take on the table array length touch.

the hash the hash = int (key.hashCode ());
int I = indexFor (the hash, table.length);
 
static int the hash (H int) {
        // This function Ensures that only by hashCodes that Differ
        // Constant Multiples each 'bit AT A bounded have have position
        // Number of Collisions (Approximately default Load factor AT. 8).
        H ^ = (H 20 is >>>) ^ (>>> 12 is H);
        return ^ H (H >>>. 7) ^ (H . 4 >>>);
    }
 
 static int indexFor (H int, int length) {
        return & H (. 1-length);
three, HashMap data storage structure
1, HashMap and a linked list arrays of data storage is achieved
HashMap employed Entry array to store the key-value pairs, each pair consisting of a key Entry entity, Entry class is actually a one-way linked list structure having next pointer, a next Entry entity may be connected, in order to resolve the conflict Hash problem.

Array storage interval is continuous, severe memory footprint, it is a great space complex. However, the array binary search time complexity is small, is O (1); Array features are: easy addressing, inserting and deleting difficulties;

Discrete list storage section, take up more relaxed memory, so the space complexity is very small, but a large time complexity of O (N). Features list are: addressing difficult, insertions and deletions easy.

 

 

 

From the graph we can see the list data structure consisting of an array +, an array of length 16, each element is stored in a linked list of the first node. Then these elements are in accordance with what rules it is stored in the array. % Len is generally obtained by hash (key.hashCode ()), i.e. the hash value of the key elements of the array obtained modulo length. In the above example, the hash table, 12, 28 = 12% 16% 16% = 12,108 16% = 12,140 16 = 12. Therefore, 12,28,108 and 140 are stored in the array index 12 for the location.

HashMap which are to achieve a static inner class Entry, its important attributes hash, key, value, next.

HashMap which uses a conceptual structure of a chain data. We mentioned above which has a next Entry class attribute points to the next action is Entry. Figuratively, the first key pair A in, by calculating its hash key obtained index = 0, denoted: Entry [0] = A. For a while and then come in a key-value pair B, by calculating the index is also equal to 0, now how do? HashMap do: B.next = A, Entry [0] = B, if they come C, index also equal to 0, then C.next = B, Entry [0] = C; and we will find that the local index = 0 In fact, access to the a, B, C three key-value pairs with them through the next link this property. So do not worry about doubt. That is stored in the array is the last element inserted. Up to this point, roughly HashMap realize that we should have been clear.

 V PUT public (Key K, V value) {
        IF (Key == null)
            return putForNullKey (value); // null always placed first in a list array
        int hash = hash (key.hashCode () );
        indexFor I = int (the hash, table.length);
        // traverse the list
        for (the Entry <K, V> Table E = [I]; E = null;! E = e.next) {
            Object K;
            // if the key in the list already exists, it is replaced with the new value
            IF (e.hash the hash == && ((K = e.key) == key.equals || Key (K))) {
                V = oldValue e.Value;
                E value = .Value;
                e.recordAccess (the this);
                return oldValue;
            }
        }
 
        ModCount ++;
        the addEntry (the hash, Key, value, I);
        return null;
    }
 
 
 
void the addEntry (int the hash, K Key, V value, int bucketIndex) {
    the Entry <K, V> E = Table [bucketIndex];
    Table [bucketIndex] = new new Entry <K, V> (hash , key, value, e); // parameters e, is Entry.next
    // if the size exceeds the threshold, the table size of the expansion. Re-hash
    IF (size ++> = threshold)
            a resize (2 * table.length);
}
D. Important depth analysis method
1, the constructor
HashMap () // constructor with no arguments
HashMap (int initialCapacity) // specified initial capacity constructor 
HashMap (int initialCapacity, float loadFactor) // specified initial capacity and load factor
HashMap (Map <? extends K ,? extends V> m) // specified set, into HashMap
HashMap configuration provides four methods, the construction method, the third method relies performed, but the first three methods do not initiate operation of the array, even if you call the constructor table length table stored in the case of array elements HaspMap still zero. In a fourth call inflateTable constructor () method completes the initialization table, and m is an element added to the HashMap.

2, a method of adding
public V PUT (Key K, V value) {
        IF (Table == EMPTY_TABLE) {// if the initialization
            inflateTable (threshold);
        }
        IF (Key == null) // number 0 is placed in position
            return putForNullKey ( value);
        int hash = hash (Key); // calculate the hash value of
        int i = indexFor (hash, table.length ); // calculate the Entry [] storage location
        for (Entry <K, V> e = table [I]; E = null;! E = e.next) {
            Object K;
            IF (e.hash the hash == && ((K = e.key) == key.equals || Key (K))) {
                oldValue e.Value = V;
                e.Value = value;
                e.recordAccess (the this);
                return oldValue;
            }
        }
 
        ModCount ++;
        the addEntry (the hash, Key, value, I); // add to the Map
        return null;
}
In this process, adding key-value pairs, first, the determination whether to initialize table, if not initialized (allocated space , Entry [] length of the array). Then the determination whether the key is null, if key == null, placed in the Entry [] is number 0 position. Calculating the Entry [] array of storage locations, determining whether the element has a position, if there is an element exists, traversed the Entry [] array location on the single list. Determining whether there is a key, if the key already exists, the new value with the value, replacing the old value point value and return the old value of the value. If the key is not present in the HashMap, program execution continues downward. The key-vlaue, Entry generating entity added to the HashMap Entry [] array.

. 3, the addEntry ()
/ *
 * the hash value of the hash
 * key key
 * value value value
 * bucketIndex Entry [] array stores the index
 * / 
void the addEntry (int the hash, Key K, V value, int bucketIndex) {
     IF (( ! size> = threshold) && (null = Table [bucketIndex])) {
         a resize (2 * table.length); // expansion operation, the position of the data element into newTable recalculated, the order of the list with the preceding Instead
         the hash = (null = Key!) the hash (Key):? 0;
         bucketIndex = indexFor (the hash, table.length);
     }
 
    createEntry (the hash, Key, value, bucketIndex);
}
void createEntry (int the hash, Key K, value V, int bucketIndex) {
    the Entry <K, V> E = Table [bucketIndex];
    Table [bucketIndex] = new new the Entry <> (the hash, Key, value, E);
    size ++;
}
added to a specific operation, prior to addition to judge the capacity, if the current capacity reaches the threshold, and need to be stored to the Entry [] array, Advanced expansion operation, space charge capacity of table length 2 times. Recalculating the hash value, and an array of storage locations, the order of the list and the list opposite sequence before expansion after expansion. Entry newly added entity is then stored to the current Entry [] position of head of the list. Prior to 1.8, the newly inserted elements are placed in the position of the head of the list, but this operation in a highly concurrent environments easily lead to a deadlock, so after 1.8, the newly inserted elements are placed in the end of the list.

4、获取方法:get
public V get(Object key) {
     if (key == null)
         //返回table[0] 的value值
         return getForNullKey();
     Entry<K,V> entry = getEntry(key);
 
     return null == entry ? null : entry.getValue();
}
final Entry<K,V> getEntry(Object key) {
     if (size == 0) {
         return null;
     }
 
     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;
}
in the get method calculated first hash value, then call indexFor () method of obtaining the key storage location in the table is to obtain a single linked list at that location, through the list to find the key and equal to the specified key content Entry, entry.value return value.

5、删除方法
public V remove(Object key) {
     Entry<K,V> e = removeEntryForKey(key);
     return (e == null ? null : e.value);
}
final Entry<K,V> removeEntryForKey(Object key) {
     if (size == 0) {
         return null;
     }
     int hash = (key == null) ? 0 : hash(key);
     int i = indexFor(hash, table.length);
     Entry<K,V> prev = table[i];
     Entry<K,V> e = prev;
 
     while (e != null) {
         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;
             the else
                 prev.next = Next;
             e.recordRemoval (the this);
             return E;
         }
         PREV = E;
         E = Next;
    }
 
    return E;
}
delete operation, the first computing Specifies the hash value of the key, and calculates the storage location in the table, it is determined whether the current position exists Entry entity, if there is no direct return, if the current Entry location entity exists, traversing the list. Entry defines three references, respectively, pre, e, next. In the course of looping through the first pre determined and e are equal, show that if equal, table current position of only one element, directly to the table [i] = next = null . If the formation of pre -> e -> next connection relationship, it is determined whether the key e is equal to the specified key and, if equal, so that pre -> next, e lost reference.

. 6, containsKey
public Boolean containsKey (Object Key) {
        return getEntry (Key) = null;!
    }
Final the Entry <K, V> getEntry (Object Key) {
        int the hash = (Key == null) 0: the hash (Key?. the hashCode ());
        for (the Entry <K, V> E = Table [indexFor (the hash, table.length)];
             E = null;!
             E = e.next) {
            Object K;
            IF (the hash == e.hash &&
                ((K = e.key) == || Key (Key = null && key.equals (K))!))
                return E;
        }
        return null;
    }
containsKey hash is calculated first and then use the hash table.length take palpable index value, traversing Table [index] Find element contain the same key value.

. 7, containsValue
public Boolean containsValue (Object value) {
    IF (value == null)
            return containsNullValue ();
 
    the Entry [] Tab = Table;
        for (int I = 0; I <tab.length; I ++)
            for (E = the Entry Tab [I]; E = null;! E = e.next)
                IF (value.equals (e.Value))
                    return to true;
    return to false;
    }
containsValue method relatively rough and is a direct traverse elements until it finds value, essentially containsValue method shows that HashMap and contains an array of common methods and list no difference, you do not expect it would be like containsKey less efficient.

Five, JDK change 1.8
1, HashMap using a linked list arrays + + red-black tree implementation.
In Jdk1.8 in HashMap implementation made some changes, but the basic idea did not become, but in some places is optimized, the following look at these changes in place, data structures by an array + linked list, change list storage array + + red-black tree, when the chain length exceeds a threshold value (8), the list is converted to red-black tree. Further increase in performance.

2, put simple analytical method:

V PUT public (Key K, V value) {
    // call PutVal () method completes
    return PutVal (the hash (Key), Key, value, to false, to true);
}
 
Final V PutVal (int the hash, Key K, V value, onlyIfAbsent Boolean,
               Boolean The evict) {
    the Node <K, V> [] Tab; the Node <K, V> P; n-int, I;
    // table determines whether initialization, an initialization operation or
    if ((tab = table) == null || (= n-tab.length) == 0)
        n-= (Tab = a resize ()) length;.
    // calculating an index stored position, if no element, direct assignment
    if ((p = tab [i = (n -. 1) & the hash]) == null)
        Tab [I] = the newNode (the hash, Key, value, null);
    the else {
        the node <K, V> E; K K;
        // if node already exists, performs assignment
        if (p.hash == hash &&
            ((K = p.key) == || Key (Key = null && key.equals (K))!))
            E = P;
        // determines whether the list is a red-black tree
        the else IF (the instanceof the TreeNode P)
            // red-black tree object operation
            E = ((the TreeNode <K, V>) P) .putTreeVal (the this, Tab, the hash, Key, value);
        the else {
            // linked list,
            for (int BinCount = 0;; ++ BinCount ) {
                IF ((E = p.next) == null) {
                    p.next the newNode = (the hash, Key, value, null);
                    // chain length of 8, the red-black tree is stored into the list
                    if (binCount> = TREEIFY_THRESHOLD -. 1) for -1 // 1st
                        treeifyBin (Tab, the hash);
                    BREAK;
                }
                // key exists, overwrite
                IF (e.hash the hash == &&
                    ((K = e.key) == || Key (Key = null && key.equals (K)))!)
                    BREAK;
                P = E;
            }
        }
        IF (! E = null) {// existing Key Mapping for
            V = oldValue e.Value;
            IF (onlyIfAbsent oldValue == null ||!)
                e.Value = value;
            afterNodeAccess (E);
            return oldValue;
        }
    }
    // record the number of modifying
    ++ modCount;
    if capacity is needed // Analyzing
    iF (size ++> threshold)
        a resize ();
    // No operation
    afterNodeInsertion (The evict);
    return null;
}
if node key exists, return the old value, if there is no Null is returned.
----------------
Disclaimer: This article is CSDN blogger original article "heicy", and follow CC 4.0 BY-SA copyright agreement, reproduced, please attach the original source link and this statement. .
Original link: https: //blog.csdn.net/qq_41345773/article/details/92066554

Guess you like

Origin blog.csdn.net/tiantian1980/article/details/102502964