HashMap底层原理小记

  public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable

  首先HashMap 继承自AbstractMap(抽象类) 实现了Map接口。

  在new HashMap<K, V>()时,通过底层代码可以知道:

public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR;
        threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
        table = new Entry[DEFAULT_INITIAL_CAPACITY];
        init();
}

  其中loadFactor为float型,指hash表的负载因子;DEFAULT_INITIAL_CAPACITY 为常量,是hash表的初始容量,初值为16;threshold为int型,是hash表的装载阈值,反应了hash表的装载程度。接下来就会初始化一个Entry类型的数组,初始容量为16;而其中table的类型Entry是Hashmap的一个静态内部类。

static class Entry<K,V> implements Map.Entry<K,V>,并且该类没有无参构造函数,
  Entry(int h, K k, V v, Entry<K,V> n) {	
            value = v;
            next = n;
            key = k;
            hash = h;
        }

 当使用hashMap.put(K,V)时, 

public V put(K key, V value) {
        if (key == null)				//判断键是否为null
            return putForNullKey(value);	//添加键为null的值
        int hash = hash(key.hashCode());	//计算二次计算key的hash值,避免冲突
        int i = indexFor(hash, table.length);	//根据hash值确定元素存放在表中的索引
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {	//定位到table中的位置后,取出当前的链表
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {	//判断key是否重复
                V oldValue = e.value;					//重复的时候返回重复key的value值
                e.value = value;
                e.recordAccess(this);				//这句的作用是什么不知道
                return oldValue;
            }
        }
        modCount++;				// modCount是不可被序列化的,具有可见性的,
//用来记录hashmap的修改次数
        addEntry(hash, key, value, i);		//添加一个键值对
        return null;
}

  

void addEntry(int hash, K key, V value, int bucketIndex) {
	Entry<K,V> e = table[bucketIndex];
        table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
        if (size++ >= threshold)
//当hash表的大小>=阈值时进行扩容,hash表的大小变为原来的两倍。
            resize(2 * table.length);
}

Entry(int h, K k, V v, Entry<K,V> n) {
            value = v;
//直接让原来table[bucketIndex]位置上的表头为新元素的next值,说明,每次插入元素都会在表位置中的表头。
            next = n;
            key = k;
            hash = h;
        }

void resize(int newCapacity) {
        Entry[] oldTable = table;
        int oldCapacity = oldTable.length;
        if (oldCapacity == MAXIMUM_CAPACITY) {//当hash表的容量到达最大值时不再扩容
            threshold = Integer.MAX_VALUE;
            return;
        }

        Entry[] newTable = new Entry[newCapacity];
        transfer(newTable);	//将原来hash表中的内容复制到新的hash表中
        table = newTable;
        threshold = (int)(newCapacity * loadFactor);
    }

  Hashmap中只允许有一个空键的原因:

  从代码中可以看出,当第二次添加空键的时候,会将原来null所对应的值覆盖,而且,空键所对应的key存放在表的第一个位置table[0].通过addEntry(0, null, value, 0),在第一次添加空键元素的时候,可以保证null所对应的元素在table[0]链表的表头。

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

 

posted @ 2018-08-24 15:08  wishboy  阅读(93)  评论(0编辑  收藏  举报