HashMap本质是一个链表散列的数据结构,即数组和链表的结合体。HashMap底层是一个数组结构,数组中的每一项又是一个链表。当新建一个HashMap的时候,就会初始化一个数组。
transient Entry[] table; static class Entry<K, V> implements Map.Entry<K, V>{ final K key; V value; Entry<K, V>next; final int hash; ..... }
put方法
先根据key算出hash值,然后根据得到的hash值来定位这个元素在数组中的位置,若数组该位置已经存放了其他元素,那么这个位置上的元素将以链表的形式存放,新加入的放在链表头,最先加入的放在链尾,如果数组在该位置没有元素,则将该元素放到数组中。
public V put(K key, V value){ if(key == null) return putForNullKey(value); int hash = hash(key.hashCode()); int i = indexFor(hash, table.length); for(Entry<K, V> e = table[i]; e != null; ){ 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 vaule, int bucketIndex){
Entry<K, V> e = table[bucketIndex];
table[bucketIndex] = new Entry<K, V>(hash, key, value, e);
if(size ++ >= threshold)
resize(2 * table.length);
}
static int hash(int h){
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
static int indexFor(int h, int length){
return h & (length - 1);
}
get()方法:先根据key算出hash值,然后根据hash值找到对应位置的某一元素,然后通过key的equlas方法在对应位置的链表中找到需要的元素
public V get(Object key){ if(key == null) return getForNullKey(); int hash = hash(key.hashCode()); 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.equals(k)))
return e.value; }
return null; }
当HashMap中的元素个数超过数组大小*loadFactor时,就会对数组进行扩容,扩大一倍后重新计算每个元素在数组中的位置。loadFactor的默认值是0.75。
loadFactor = 散列表的实际元素数目/散列表的容量。负载因子衡量的是一个散列表的空间的使用程度,负载因子越大表示散列表的装填程度越高。对于使用链表法的散列表来说,查找一个元素的平均时间是O(1+a),若负载因子太大,对空间利用充分,但查找效率较低;若负载因子太小,那么散列表的数据将过于稀疏,对空间进行严重浪费。
threshold = (int) (capacity * loadFactor);超过threshold会重新resize,以降低实际的负载因子
java.util.HashMap是不安全的,若在使用迭代器的过程中有其他线程修改了map,则抛出ConcurrentModificationException,这是所谓的fail-fast策略。每次对HashMap内容的修改都将增加modCount域,在迭代器初始化过程中会将这个值赋给迭代器expectedModCount。
HashIterator(){ expectedModCount = modCount; if(size > 0) { Entry[] t = table; while(index < t.length && (next = t[index++]) == null) ; }
final Entry<K, V> nextEntry(){
if(modCount != expectedModCount)
throw new ConcurrentModificationException();
} }
在所有HashMap类的collection视图方法所返回的迭代器都是fail-fase的:在迭代器创建之后,若从结构上对映射进行修改,除非通过迭代器本身的remove方法,其他任何时间任何方式的修改,迭代器都会抛出ConcurrentModificationException。
JDK1.8改版HashMap
从结构实现上由数组+链表改为数组+链表+红黑树(当链表长度大于8时转换为红黑树)
static class Node<K, V> implements Map.Entry<K, V>{
final int hash;
final K key;
V value;
Node<K, V> next;
Node<int hash, K key, V value, Node<K, V> next){...}
public final K getKey(){
return key;
}
public final V getValue(){
return value;
}
public final String toString(){
return key + "=" + value;
}
public final int hashCode(){
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
public final V setValue(V newValue){
V oldValue = value;
value = newValue;
return oldValue;
}
public final boolean equals(Object o){
if(o == this)
return true;
if(o instanceof Map.Entry){
Map.Entry<?, ?> e = (Map.Entry<?, ?>)o;
if(Objects.equals(key, e.getKey()) && Objects.equals(value, e.getValue()))
return true;
}
return false;
}
}
put()方法
public V put(K key, V value){ return putVal(hash(key), key, value, false, true); } final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict){
Node<K, V>[] tab;
Node<K, V> p;
int n, i;
if((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if((p = tab[i = (n -1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else{
Node<K, V> e;
K k;
if(p.has == hash && ((k = p.key) == key || (key != null && key.equals(k)))
e = p;
else if(p instanceof TreeNode)
e = ((TreeNode<K, V>)p).putTreeVal(this, tab, hash, key, value);
else{
for(int binCount = 0; ; ++binCount){
if((e = p.next) == null){
p.next = newNode(hash, key, value, null);
if(binCount >= TREEIFY_THRESHOLD - 1)
treeifyBin(tab, hash);
break;
}
if(e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if(e != null){
V oldValue = e.value;
if(!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if(++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
resize():重新计算容量,向HashMap对象里不停的添加元素,而HashMap对象内部的数组无法装载更多的元素时,对象就需要扩容。Java的数组是无法自动扩容的,只好使用一个新的大的数组代替已有的容量小的数组。扩容时使用的是2次幂的扩展,因此元素的位置要么是在原位置,要么是在原位置再移动2次幂的位置。在JDK1.8中就无需重新计算hash,只需要看原来的hash值新增的那个bit是1还是0,是0的话索引没变,是1的话索引编程“原索引 + oldCap”。
final Node<K, V>[] resize(){
Node<K, V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
int newCap, newThr = 0;
if(oldCap > 0){
if(oldCap >= MAXIMUM_CAPACITY){
threshold = Integer.MAX_VALUE;
return oldTab;
}else if((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY){
newThr = oldThr << 1;
}
}else if(oldThr > 0){
newCap = oldThr;
}else{
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
if(newThr == 0){
float ft = (float) newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? (int)ft : Integer.MAX_VALUE);
}
threshold = newThr;
@SuppressWarnings({"rawtypes", "uncheck"})
Node<K, V>[] newTab = (Node<K, V>[])new Node[newCap];
table = newTab;
if(oldTab != null){
for(int j = 0; j < oldCap; j++){
Node<K, V> e;
if(e.next == null)
newTab[e.hash & (newCap -1)] = e;
else if(e instanceof TreeNode)
((TreeNode<K, V>)e).split(this, newTab, j, oldCap);
else{
Node<K, V> loHead = null, loTail = null;
Node<K, V> hiHead = null, hiTail = null;
Node<K, V> next;
do{
next = e.next;
if((e.hash & oldCap) == 0){
if(loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}else{
if(hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
}while((e = next) != null);
if(loTail != null){
loTail.next = null;
newTab[j] = loHead;
}
if(hiTail != null){
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
return newTab; }
get()方法
public V get(Object key){ Node<K, V> e; return (e = getNode(hash(key), key)) == null ? null : e.value; }
final Node<K, V> getNode(int hash, Object key){
Node<K, V> tab;
if((tab = table) != null && (n = tab.length) > 0 && (first = tab[(n -1) & hash]) != null){
if(first.hash == hast && ((k = first.ley) == key) || (key != null && key.equals(k)))
return first;
if((e = first.next) != null){
if(first instanceof TreeNode)
return ((TreeNode<K, V> first).getTreeNode(hash, key));
do{
if(e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
return e;
}while((e = e.next) != null);
}
}
return null;
}