HashMap源代码分析 大家在项目中很频繁的用到了java.util.HashMap 类,但是你对其内部实现是否了解呢?最近我分析了一下该类的源码,抛砖引玉,和大家分享讨论一下。 1、 HashMap的基本属性及数据结构 HashMap的基本数据结构是数组,而数组元素是链表,其元素类型是Entry。HashMap是根据对key的hash运算决定将Entry放在数组的哪个位置上的,而对于hash值相同的元素,就会放在同一个链表中。 HashMap中有一个声明为“transient Entry[] table”的属性,Entry是HashMap存储的基本数据类,其基本属性如下: final K key; V value; Entry<K,V> next; final int hash; key和value自然不用说,hash是key的hash值,next的类型是Entry,它存在的价值就是解决hash冲突的!如果put一个key-value对时,经过hash运算,该K-V对对应的EntryA应该放在Entry[] table中第5的位置,但是该位置已经有Entry B存在了,那么就将A.next = B,A放在第5的位置上。如下图所示:
HashMap中还有几个属性: 默认容量:static final int DEFAULT_INITIAL_CAPACITY = 16; 最大容量: static final int MAXIMUM_CAPACITY = 1 << 30; 默认加载因子: static final float DEFAULT_LOAD_FACTOR = 0.75f; 扩容因子:int threshold;(当容量超过threshold时,扩容,threshold = loadFactor* capacity) 加载因子:final float loadFactor; 我们可以通过分析如下代码来了解HashMap的初始化过程: public HashMap(int initialCapacity, float loadFactor) { if (initialCapacity < 0){ throw newIllegalArgumentException("Illegal initial capacity: " + initialCapacity);} if (initialCapacity > MAXIMUM_CAPACITY){ initialCapacity = MAXIMUM_CAPACITY;} if (loadFactor <= 0 || Float.isNaN(loadFactor)){ throw newIllegalArgumentException("Illegal load factor: " + loadFactor);}
// Find a power of 2 >= initialCapacity int capacity = 1; while (capacity < initialCapacity){ capacity <<= 1;}
this.loadFactor = loadFactor; threshold = (int)(capacity *loadFactor); table = new Entry[capacity]; init(); } 该构造函数的参数是我们期望的初始化容量initialCapacity和装载因子loadFactor。 我们通过 while (capacity <initialCapacity){ capacity <<= 1;} 这段代码可以了解到,capacity是大于initialCapacity的最小2次幂数值。也就是说,如果我们的参数initialCapacity = 10,loadFactor = 0.8,那么实际上capacity = 16,该HashMap的初始容量是16,当元素个数超过10 * 0.8 = 8的时候,map进行扩容。
int hash = hash(key.hashCode()); int i = indexFor(hash, table.length); static intindexFor(int h, int length) { return h& (length-1); }
从上面代码可以看出,i 的值就是元素处于table中的位置,i 是由hash和length计算出来的。
下面来看一下HashMap中的put/get/remove方法实现。 2、put/get/remove操作如何实现 先看下put方法的源码
public V put(K key, V value) { //当key=null,调用putForNullKey方法,该方法默认将key=null的值放在table首位 if (key == null) return putForNullKey(value); //计算hash值 int hash = hash(key.hashCode()); //计算存储的位置 int i = indexFor(hash, table.length); //遍历table[i]处已经存在的元素 for (Entry<K,V> e = table[i]; e != null; e = e.next) { Object k; //如果该元素的key已经存在,则替换value值,同时返回原始值 if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } //如果该元素的key不存在,执行插入操作,返回null modCount++; addEntry(hash, key, value, i); return null; } |