#HashMap源码个人理解 HashMap extends AbstractMap implements Map
Map接口提供以下常用方法:
Map
- size()
- isEmpty()
- containsKey()
- containsValue()
- get()
- put()
- remove()
- putAll()
- clear()
- keySet()
- values()
- equals()
- hashCode()
- I Entuty<K,V>
AbstractMap实现了上述常用方法。
HashMap在AbstractMap的基础上添加并优化了一些常用方法, 并重写了AbstractMap的部分方法, 使HashMap具有自己的特性。下面是一些HashMap的特性:
-
loadFactor和initialCapacity 在构造函数中传入该值。 initialCapacity为初始容量, 默认为1<<4 = 16 个。注意:当Map容量大于2^32-1个是 Map数量不再增加。(此时估计内存也会被撑爆)。
loadFactor为Map膨胀系数, 当map达到一定容量之后每次增加计算方法为:1、(putAll时这样计算)int targetCapacity = (int)(numKeysToBeAdded / loadFactor + 1); 2、添加元素时 resize方法 resize(2 * table.length);一次扩容两倍。这里的容量由loadFactor决定, 计算公式为:threshold = (int) Math.min(capacity * loadFactor, 2^31);
-
resize()方法
//重新扩容当前的集合 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); }
这个是transfer方法
//将老数据转移到新的数组里
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) {
//重新计算hash
e.hash = null == e.key ? 0 : hash(e.key);
}
//根据新数组的长度, 重新计算元素的在数组中的index
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];//**如果当前节点已经有数据了, 就把这个数据放到该entity的next节点上, 这里很重要, 这就是解决hashmap hash相同的解决方案**
newTable[i] = e;
e = next;//遍历整个链表
}
}
}
-
以数组作为存储的容器。
//创建一个Entity数组, 所有的数据都是存在这个数组里的, 这个数组的index根据key的hash计算而来。 transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE; //计算数组index的方法: //根据key的hash 和length的位运算找到index static int indexFor(int h, int length) { // assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2"; return h & (length-1);
}
-
所有的put、get等都先判断key值是否等于null。因为null值的index = 0 如:e.hash = null == e.key ? 0 : hash(e.key);
//根据key找entity,根据key就可以确定数组的index. 只需要遍历所有的next节点找到相同的key即可。 final Entry<K,V> getEntry(Object key) { if (size == 0) { return null; } int hash = (key == null) ? 0 : hash(key); for (Entry<K,V> e = table[indexFor(hash, table.length)]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) return e; } return null; } //put元素 先确定这个元素的index, 然后找这个元素上是不是已经有节点了, 如果有节点了直接插入值,否则创建新元素 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; } //判断容器是否需要重新扩容 void addEntry(int hash, K key, V value, int bucketIndex) { if ((size >= threshold) && (null != table[bucketIndex])) { 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++; }