Java based HashMap1.7 source code interpretation

HashMap jdk1.7

Three basic variables

//必须是2的幂次方
 static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
 //装载的元素/容量
 static final int MAXIMUM_CAPACITY = 1 << 30;
 static final float DEFAULT_LOAD_FACTOR = 0.75f

Why the default capacity must be a power of 2?
Answer: hashcode & (length-1)
uses a division hash, and a certain bit of the AND is 0, then with this bit and you can never get 1, then some positions are always vacant

initialization

Initial capacity and load factor

 public HashMap(int initialCapacity, float loadFactor) {
        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;
        threshold = initialCapacity;
        init();
    }

put method

Head insertion

 public V put(K key, V value) {
 		//当table不存在的时候
        if (table == EMPTY_TABLE) {
            inflateTable(threshold);
        }
        if (key == null)
            return putForNullKey(value);
        int hash = hash(key);
        int i = indexFor(hash, table.length);
		//头插法
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            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;
            }
        }

        modCount++;
        addEntry(hash, key, value, i);
        return null;
    }

inflateTable method

    /**
     * Inflates the table.
     * table扩张
     */
    private void inflateTable(int toSize) {
        // Find a power of 2 >= toSize
        //不管初始容量设的是不是2的幂次,都会自动转为2的幂次
        int capacity = roundUpToPowerOf2(toSize);
		//扩张的阈值为capacity*loadFactor
        threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
        //table是一个Entry数组
        table = new Entry[capacity];
        initHashSeedAsNeeded(capacity);
    }

table is an Entry array,
regardless of whether the initial capacity is set to a power of 2, it will automatically be converted to a power of 2.

Method of inserting null value

    private V putForNullKey(V value) {
        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;
    }

Null values ​​can only be inserted into the bucket at 0. There can only be one, and inserting it again will overwrite the value

hash

null hash value is always 0

    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);
    }

indexFor () division hash

The reason why the capacity must be a non-zero power of 2

 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);
    }

addEntry ()

When the number of elements is greater than threshold = capacity * loadFactor, 2 times expansion, why 2 times is equivalent to capacity must be a non-zero power of 2 index = hash & (length-1)
1.7 Element migration after expansion is recalculated hash

void addEntry(int hash, K key, V value, int bucketIndex) {
        if ((size >= threshold) && (null != table[bucketIndex])) {
            resize(2 * table.length);
            hash = (null != key) ? hash(key) : 0;
            bucketIndex = indexFor(hash, table.length);
        }

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

resize()

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];
        transfer(newTable, initHashSeedAsNeeded(newCapacity));
        table = newTable;
        threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
    }

transfer()

The old table is migrated to the new table, and the hash value is recalculated

    void transfer(Entry[] newTable, boolean rehash) {
        int newCapacity = newTable.length;
        for (Entry<K,V> e : table) {
            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;
            }
        }
    }

get method omitted

Published 8 original articles · Like1 · Visits 168

Guess you like

Origin blog.csdn.net/qq_41725735/article/details/105278968