今天心情异常的差,正好总结一下前期学习的hashmap知识,学习能使人快乐.
- <p><strong>Note that this implementation is not synchronized.</strong>
- If multiple threads access a hash map concurrently, and at least one of
- the threads modifies the map structurally, it <i>must</i> be
- synchronized externally. (A structural modification is any operation
- that adds or deletes one or more mappings; merely changing the value
- associated with a key that an instance already contains is not a
- structural modification.) This is typically accomplished by
- synchronizing on some object that naturally encapsulates the map.
大致就是说,此实现不是同步的,在多线程操作下,需要在外部加上synchronize。
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; Node<K,V> first, e; int n; K k;
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
if (first.hash == hash && // always check first node
((k = first.key) == 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;
}
在get方法中调用了一个final方法getNode,getNode中判断key和key的hash值是否在哈希桶中有相等的,如果都相等的话就返回value。
public V put(K key, V value) {
/*
使用put(key,value)进行操作时,会先计算key的hash值,再调用putval方法
*/
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;
/*
如果哈希表是空的就进行初始化resize操作
*/
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
/*
如果没有产生碰撞,可以简单的理解为哈希表中没有相等的hash,就直接存入hash桶中
*/
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
/*
底下开始处理产生碰撞的情况
*/
else {
Node<K,V> e; K k;
/*
如果产生了碰撞,并且hash和key都相等,为了保证key的唯一性,就直接覆盖原节点
*/
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
/*
如果产生了碰撞,也没有相等的节点key,但是属于红黑树节点,就新增一个红黑树节点
*/
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
/*
如果产生了碰撞,也没有相等的节点key,也不是红黑树节点,就转为链表
*/
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
/*
找到最后一个节点插入,尾插
*/
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
/*
如果在遍历过程中发现有相等的节点就直接覆盖
*/
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
/*
长度大于threshold进行扩容
*/
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
resize扩容其实就是把原长度扩大到原来的两倍,并将原来的节点重新进行hash操作之后放入新的哈希表中。但是扩容操作是比较占资源的操作,在初始化时最好就进行指定大小。
hashmap中计算索引的方法:(jdk1.7版本中是indexFor函数,但是jdk1.8取消了,直接进行操作)
hashcode(key) & (length- 1)
当扩容之后,长度发生变化,此时的索引值和原来的不相等,需要重新计算。
初始长度为16。当长度为2^n时,在计算index时length-1的二进制全是1,既能加快运算的速度,也可以保证均匀分布