欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

HashMap 源码分析

程序员文章站 2022-06-14 08:13:24
...

     在HashMap的API中定义中有具体说明“Note that this implementation is not synchronized.”,此类是不同步方法,HashMap数据结构在单线程应用中可正常使用。在blog中看到有人在并发环境中使用HashMap时,出现过占用CPU100%问题,结合HashMap源码我们对出现占用CPU问题进行分析。

 

一、创建HashMap

 

  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();
    }
    public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }
    public HashMap() {
        this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
    }

threshold=initialCapacity:设置容量,默认(DEFAULT_INITIAL_CAPACITY=16)

 

loadFactor:设置负载因子,默认(DEFAULT_LOAD_FACTOR=0.75)

 

二、存值

 

public V put(K key, V value) {
        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;
    }

 首先判断table属性是否进行初始化容量大小,使用inflateTable方法进行初始化容量:

 

 

private void inflateTable(int toSize) {
        //计算toSize大于等于最接近number的2的冪数
        int capacity = roundUpToPowerOf2(toSize);
        //用计算出的容量*负载因子,获得下次调整的容量大小
        threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
        //初始化table属性
        table = new Entry[capacity];
        initHashSeedAsNeeded(capacity);
    }

 threshold 为第一步设置的容量的值,设置完容量后继续向下执行:

 

 

        //hashMap允许存储null值的原因
        if (key == null)
            return putForNullKey(value);
        //获取Key的hash值
        int hash = hash(key);
        //根据Key的hash值和table属性的容量计算出key该放在table中的索引坐标
        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;
            }
        }

 获取到key在table中的索引,取出table中该索引对应的key和value信息,如果key和当前key相同,将新value替换oldvalue并将oldValue返回。

 

 

        //记录hashMap修改的次数
        modCount++;
        将key 和 value键值对添加到table属性中
        addEntry(hash, key, value, i);

三、储存键值对

 

 

    void addEntry(int hash, K key, V value, int bucketIndex) {
        //当前table的长度大于或等于根据负载因子计算的下一次扩充的值时
        if ((size >= threshold) && (null != table[bucketIndex])) {
            //table 长度重新扩充,并重新获取key的新的hash值及其新的索引
            resize(2 * table.length);
            hash = (null != key) ? hash(key) : 0;
            bucketIndex = indexFor(hash, table.length);
        }

        createEntry(hash, key, value, bucketIndex);
    }
    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++;
    }
    static class Entry<K,V> implements Map.Entry<K,V> {
        final K key;
        V value;
        Entry<K,V> next;
        int hash;
        Entry(int h, K k, V v, Entry<K,V> n) {
            value = v;
            next = n;
            key = k;
            hash = h;
        }
		......
	}

 在看到createEntry方法的源码时,我们就能将HashMap中的数据模型构建出来,即数组与链表相结合使用,当存在多个hash值一样时,存在数组table的同一个索引上,放在该索引上存储的链表的第一个位置上。

结构如下所示,通过向链表顶部添加来解决hash冲突问题。

table[0]=Entry<?,?>
table[1]=Entry<?,>=>next=>Entry<?,?>
table[2]=Entry<?,>=>next=>Entry<?,?>=>next=>Entry<?,?>

接下来我们看下resize方法,resize方法主要作用是对table属性进行扩容

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

 当HashMap的长度满时 

 

 

 

 

 

 

 

 

 

 

相关标签: HashMap