-
HashMap
Hash table based implementation of the Map interface. This
implementation provides all of the optional map operations, and permits
null values and the null key. (The HashMap class is roughly equivalent
to Hashtable, except that it is unsynchronized and permits nulls.)
This class makes no guarantees as to the order of the map; in particular,
it does not guarantee that the order will remain constant over time.Official Document Description Information: Based on Map interface implementation, key values are allowed to null, non-threaded synchronization, not arranged in insertion order, and not guaranteed to change with time.
The underlying data structure implementation of HashMap is array plus linked list, each item of the array is a chain.
-
Constructor
HashMap provides four constructors:
HashMap (int initial capacity, float loadFactor): Construct an empty HashMap with specified capacity and load factor.
HashMap (int initial capacity): Construct an empty HashMap with a specified capacity and a default load factor of 0.75
HashMap(): Construct an empty HashMap with a default capacity of 16 and a default load factor of 0.75.
HashMap (map <? Extends K,? Extends V > m): Construct an empty HashMap that matches all elements in all maps and has a loading factor of 0.75.
Initial capacity and load factor are important parameters affecting HashMap performance.
Initial capacity: capacity when creating hash tables (bucket)
Loading factor: The hash table can reach a scale of how full it is before its capacity increases automatically.
-
Implementation of put()
put() general idea:
- hash the hashCode() of the key, and then calculate the index
- If you don't encounter it, put it directly in the bucket
- If the collision occurs, the buckets are stored in the form of a linked list
- If the collision causes the list to be too long (greater than or equal to TREEIFY_THRESHOLD), the linked list is converted into a red-black tree.
- Replace old value if the node already exists (guaranteeing the uniqueness of key)
- If the bucket is full (more than the load factor * current capacity), resize
public V put(K key, V value) { // hash() for hashCode() of key return putVal(hash(key), key, value, false, true); } final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node<K,V>[] tab; Node<K,V> p; int n, i; // If table is empty, create it if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; // Calculate index and do special processing if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); else { Node<K,V> e; K k; // If hash and key are the same if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; // If the chain is a tree else if (p instanceof TreeNode) e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); // If the chain is linked list else { for (int binCount = 0; ; ++binCount) { if ((e = p.next) == null) { p.next = newNode(hash, key, value, null); // If the length of the list exceeds this threshold, if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st treeifyBin(tab, hash); break; } if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } } // If a node exists, it replaces the new value and returns the old value. if (e != null) { // existing mapping for key V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; } } ++modCount; // If the size exceeds the current capacity of the load factor, the capacity is expanded. if (++size > threshold) resize(); afterNodeInsertion(evict); return null; }
-
Implementation of get()
The general idea of get():
- The first node in the bucket hits directly.
- If there is a conflict, the key.equals(k) is used to find the corresponding entry.
If it is a tree, it is searched by key.equals(k) in the tree.
If it is a linked list, it is searched by key.equals(k) in the linked list.
public V get(Object key) { Node<K,V> e; return (e = getNode(hash(key), key)) == null ? null : e.value; } final Node<K,V> getNode(int hash, Object key) { Node<K,V>[] tab; Node<K,V> first, e; int n; K k; // Table does not do the following for empty, but returns null directly if table is null if ((tab = table) != null && (n = tab.length) > 0 && (first = tab[(n - 1) & hash]) != null) { // Direct hit if (first.hash == hash && // always check first node ((k = first.key) == key || (key != null && key.equals(k)))) return first; if ((e = first.next) != null) { // Hit in the Tree if (first instanceof TreeNode) return ((TreeNode<K,V>)first).getTreeNode(hash, key); // Hit in the list do { if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) return e; } while ((e = e.next) != null); } } return null; }
- Implementation of hash()
static final int hash(Object key) { int h; // hash calculation using hashCode of key return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
The purpose of this function is to make an exclusive OR for high 16 bits, low 16 bits and high 16 bits. When calculating the subscription, it is realized as follows:
tab[i = (n - 1) & hash] // Use & Operate, not% Operate
-
Implementation of resize()
When put, it resize s when the limit is exceeded. However, because we use the expansion of the second power (i.e. the length is twice as long as the original), the position of the element is either in situ, or in situ, the position of the second power is moved again.
-
Interview questions
-
What are the characteristics of HashMap?
Based on the implementation of Map interface, when storing key-value pairs, he can accept null key values, which are asynchronous. HashMap stores Entry objects.
-
Do you know how HashMap works?
Objects are acquired through put and get storage through hash method. When we pass k/v to the put method, we get the location of the bucket by getting the hashCode of K and calculating the hash value. For further storage, the HashMap will expand automatically according to the occupancy of the current bucket (when the load factor * current capacity is exceeded, it will expand to twice the current capacity). When we get the object, we pass K to get method, get the hashCode of K and calculate the hash value to get the location in the bucket, and call to get equals() to get the key-value pair. In Java 8, when the storage capacity of a bucket exceeds a certain limit (default is 8), a red-black tree will be used to replace the list, thus increasing the speed.
-
Do you know the principle of get and put? What's the use of equals() and hashCode?
By hashing the hashCode() of key and calculating subscripts through (n - 1 & hash), when collision occurs, the key.equals() method is used to find the corresponding key-value pairs in the list or tree.
-
Do you know the implementation of hash? Why does this happen?
(h = key.hashCode()) ^ (h >>> 16)
In this way, when bucket n is relatively small, it can also ensure that the highland bit s are involved in hash calculation without too much overhead.
-
What if the size of HashMap exceeds the capacity defined by the load factor?
When the load factor is exceeded, a HashMap twice the original length is resize d and the hash method is called again.
-
Reference article: