HashMap source code analysis

HashMap is an implementation class under the Map interface. Analyzing its source code is mainly from the following steps

Inheritance relationship, data structure type, constructor, property, common method

Inheritance relationship

public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable 
//haspMap inherits AbstractMap, which implements Map interface, clone interface and serialization interface

data structure

Hash table (array + linked list)  

//Data storage location  
transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
    static class Entry<K,V> implements Map.Entry<K,V> {
        final K key;
        V value;
        Entry<K,V> next;
        int hash;
    }

Constructor

    //Incoming custom capacity size and load factor

public HashMap(int initialCapacity, float loadFactor) {
            //Check the validity of parameters
        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);
        this.loadFactor = loadFactor;
        this.threshold = tableSizeFor(initialCapacity);
    }


//Custom capacity size
    public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }

//Nonparametric structure
 public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }

//Instantiate a collection of Map type to add elements
  public HashMap(Map<? extends K, ? extends V> m) {
        this.loadFactor = DEFAULT_LOAD_FACTOR;
        putMapEntries(m, false);
    }

Attribute introduction

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; //Default array size
   
    static final int MAXIMUM_CAPACITY = 1 << 30;//Maximum capacity

    static final float DEFAULT_LOAD_FACTOR = 0.75f;//Loading factor

Research on put method

public V put(K key, V value) {
        if (table == EMPTY_TABLE) {
            //When the table array is empty, the initialization table data will be entered. When the put operation is called for the first time, the initialization table data will be entered
            inflateTable(threshold);
        }
        
        //key is null. Store the data in slot 0
        if (key == null)
            return putForNullKey(value);
        
        //Processing when key is not null
        //Find the location of the corresponding storage card slot through the key
        int hash = hash(key);
        int i = indexFor(hash, table.length);
        
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            //Find the data linked list of card slot i through position i, and traverse from the beginning to find out whether the key exists
            //The judgment conditions are hash and key, and the equal value is updated
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
       //The key does not exist in position i, so you need to add an entry node to store data
        modCount++;
        addEntry(hash, key, value, i);
        return null;
    }

private V putForNullKey(V value) {
      //If the key is null, the hash position will be No. 0. You need to traverse the linked list of No. 0 card slot and judge whether the key is null
      //Update the value in the entry and return it to the old value
      //If it does not exist, create an entry entity to store the put data
        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;
            }
        }
        modCount++;
        addEntry(0, null, value, 0);
        return null;
    }

//Find the corresponding card slot through the key hash
final int hash(Object k) {
        int h = hashSeed;
        if (0 != h && k instanceof String) {
            return sun.misc.Hashing.stringHash32((String) k);
        }

        h ^= k.hashCode();

        // This function ensures that hashCodes that differ only by
        // constant multiples at each bit position have a bounded
        // number of collisions (approximately 8 at default load factor).
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }

//Find the position in the hash table through the hash value of the key
static int indexFor(int h, int length) {
        // assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";
    
        return h & (length-1);
    }
    
    void addEntry(int hash, K key, V value, int bucketIndex) {
        //When the stored data size is greater than the capacity expansion threshold, capacity expansion is performed
        if ((size >= threshold) && (null != table[bucketIndex])) {
            //2X expansion of HashMap
            resize(2 * table.length);
            hash = (null != key) ? hash(key) : 0;
            //Get the position of the new card slot that the current key should be inserted into
            bucketIndex = indexFor(hash, table.length);
        }

        createEntry(hash, key, value, bucketIndex);
    }

//Capacity expansion
void resize(int newCapacity) {
        Entry[] oldTable = table;
        int oldCapacity = oldTable.length;
        if (oldCapacity == MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return;
        }

       //The newly created array is twice as large as the original one
        Entry[] newTable = new Entry[newCapacity];
        //Re hash each entry entity in the original map to the new map
        transfer(newTable, initHashSeedAsNeeded(newCapacity));
        table = newTable;
        //threshold = table.length*loadFactor
        threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
    }


//Insert data using header interpolation
void createEntry(int hash, K key, V value, int bucketIndex) {
        Entry<K,V> e = table[bucketIndex];
        table[bucketIndex] = new Entry<>(hash, key, value, e);
        size++;
    }

Through the above method source code analysis, we can know the steps and processes of the put method:

1. Judge whether the table has been created. If there is no table, call   The inflateTable() method initializes the table

2. Judge whether the key is empty. If it is empty, call putForNullKey to insert data

2. 1. If the key is null, start traversing the data from position 0 in the table. If a node with null key is found, overwrite the value in the node and return the value in the original node. If it is not found, create a node and add data directly.

2. 2. The key is not null. Calculate the hash value of the key, that is, calculate where the data should exist in the table

3. Judge whether the calculated hash value corresponds to the position in the table. If so, traverse the linked list under the position, and judge whether the key already exists in the linked list. If so, update the value corresponding to the key, otherwise create a node for direct insertion

4. If there is no data at this location in the table, the addEntry method is used to add elements

4. 1 if the number of data in the current table is less than 0.75*table.length, create a node directly and insert the data using header interpolation

4. 2. If the data size of the current table is greater than 0.75*table.length (threshold), the capacity will be expanded. The expansion size will be twice the length of the original table, and then call resize to re hash. Finally, the header insertion method will be used to insert data

Tags: Java Back-end

Posted on Sun, 28 Nov 2021 15:58:31 -0500 by dangyadang