HashMap源码解读

from:HashMap源码解读


一、创建一个HashMap都做了哪些工作?

Map<String,String> map = new HashMap<String,String>();

HahMap无参构造方法
  1. public HashMap() {

  2.        this.loadFactor = DEFAULT_LOAD_FACTOR;

  3.        threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);

  4.        table = new Entry[DEFAULT_INITIAL_CAPACITY];

  5.        init();

  6.    }
可以看到设置了加载因子(默认0.75)、阈值(默认容量16*默认加载因子0.75=12)、table是HashMap内部数据存储结构Entry数组。当HashMap的size大于等于阈值时,HashMap会扩容到原来的容量的2倍。而如果 new HashMap(1),它的容量是不是1呢?可以看到最终调用的构造方法:


  1. public HashMap(int initialCapacity) {

  2.         this(initialCapacity, DEFAULT_LOAD_FACTOR);

  3.     }

最终调用的是下面的
  1. public HashMap(int initialCapacity, float loadFactor) {

  2.         if (initialCapacity < 0)

  3.             throw new IllegalArgumentException("Illegal initial capacity: " +

  4.                                                initialCapacity);

  5.         if (initialCapacity > MAXIMUM_CAPACITY)

  6.             initialCapacity = MAXIMUM_CAPACITY;

  7.         if (loadFactor <= 0 || Float.isNaN(loadFactor))

  8.             throw new IllegalArgumentException("Illegal load factor: " +

  9.                                                loadFactor);


  10.         // Find a power of 2 >= initialCapacity

  11.         int capacity = 1;

  12.         while (capacity < initialCapacity)

  13.             capacity <<= 1;


  14.         this.loadFactor = loadFactor;

  15.         threshold = (int)(capacity * loadFactor);

  16.         table = new Entry[capacity];

  17.         init();

  18.     }

可以看到HashMap的size总是2的倍数,即new HashMap(1)的最终容量是2.

Entry是什么呢?Entry就是Map中存放的key-value对,并且保存了下一个节点的引用next和key的hash值,也就实现了链表的功能。

Entry结构体:



HashMap存储结构图:





二、put(key,value)的实现


  1. public V put(K key, V value) {

  2.         if (key == null)

  3.             return putForNullKey(value);

  4.         int hash = hash(key.hashCode());

  5.         int i = indexFor(hash, table.length);

  6.         for (Entry<K,V> e = table[i]; e != null; e = e.next) {

  7.             Object k;

  8.             if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {

  9.                 V oldValue = e.value;

  10.                 e.value = value;

  11.                 e.recordAccess(this);

  12.                 return oldValue;

  13.             }

  14.         }
  15.         modCount++;

  16.         addEntry(hash, key, value, i);

  17.         return null;

  18.     }

HashMap允许存null key值,它会把key为null放到table[0]的位置,即第一个bucket(table数组中的元素称为bucket桶)。

添加一个新元素时,先根据key值计算出hash码,根据hash码计算出所在bucket的位置i。遍历table[i]下挂的entry,如果key值存在就覆盖value,不存在就添加新的entry。
  1. void addEntry(int hash, K key, V value, int bucketIndex) {

  2.     Entry<K,V> e = table[bucketIndex];

  3.         table[bucketIndex] = new Entry<K,V>(hash, key, value, e);

  4.         if (size++ >= threshold)

  5.             resize(2 * table.length);

  6.     }
  1. Entry(int h, K k, V v, Entry<K,V> n) {

  2.            value = v;

  3.            next = n;

  4.            key = k;

  5.            hash = h;

  6.        }

new Entry()会把当前的Entry作为新创建的Entry的下一个结点链接起来。

addEntry方法中判断容量是否超过阈值,若超过会扩容到原来的2倍大小。

  1. void resize(int newCapacity) {

  2.        Entry[] oldTable = table;

  3.        int oldCapacity = oldTable.length;

  4.        if (oldCapacity == MAXIMUM_CAPACITY) {

  5.            threshold = Integer.MAX_VALUE;

  6.            return;

  7.        }


  8.        Entry[] newTable = new Entry[newCapacity];

  9.        transfer(newTable);

  10.        table = newTable;

  11.        threshold = (int)(newCapacity * loadFactor);

  12.    }

扩容之后把原来的元素转移过来,重新hash,重新分配bucket位置,同一个bucket链接的entry会倒序链接起来。


  1. void transfer(Entry[] newTable) {

  2.         Entry[] src = table;

  3.         int newCapacity = newTable.length;

  4.         for (int j = 0; j < src.length; j++) {

  5.             Entry<K,V> e = src[j];

  6.             if (e != null) {

  7.                 src[j] = null;

  8.                 do {

  9.                     Entry<K,V> next = e.next;

  10.                     int i = indexFor(e.hash, newCapacity);

  11.                     e.next = newTable[i];

  12.                     newTable[i] = e;

  13.                     e = next;

  14.                 } while (e != null);

  15.             }

  16.         }

  17.     }

三、get(key)的实现

get就比较简单,按照hash码找到对应的bucket,遍历挂载的entry,若key值相同就返回对应的value,若找不到就返回null。
  1. public V get(Object key) {

  2.        if (key == null)

  3.            return getForNullKey();

  4.        int hash = hash(key.hashCode());

  5.        for (Entry<K,V> e = table[indexFor(hash, table.length)];

  6.             e != null;

  7.             e = e.next) {

  8.            Object k;

  9.            if (e.hash == hash && ((k = e.key) == key || key.equals(k)))

  10.                return e.value;

  11.        }

  12.        return null;

  13.    }

四、hash冲突处理方法

1、开放地址法

2、链地址法

HashMap处理hash冲突采用的链地址法