HashMap 源码分析

阅读更多

     在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 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 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 e = table[bucketIndex];
        table[bucketIndex] = new Entry<>(hash, key, value, e);
        size++;
    }
    static class Entry implements Map.Entry {
        final K key;
        V value;
        Entry next;
        int hash;
        Entry(int h, K k, V v, Entry 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 e : table) {
            while(null != e) {
                Entry 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)