HashMap
概述
HashMap是基于Map接口实现的,允许有null的键/值,非同步,不保证有序(比如插入的顺序),不保证顺序不随时间变化。
两个重要参数
在HashMap中有两个重要的参数,分别是Capacity(容量)和Load factor(负载因子)。
Capacity就是bucket的大小,Load factor就是bucket填满程度的最大比例。如果对迭代性能要求很高的话,不要把Capacity设置过大,也不要把Load factor设置过小。当bucket中的entries的数目大于capacity * load factor时,就需要调整bucket的大小为当前的两倍。
put()方法
put()方法大致的思路如下:
1 . 对key的hashCode()方法做hash,然后再计算index;
2 . 如果没碰撞就直接放到bucket里;
3 . 如果碰撞了,以链表的形式存在bucket后;
4 . 如果碰撞导致链表过长(大于等于TREEIFY_THRESHOLD),就把链表转换成红黑树;
5 . 如果节点已经存在就替换old value(保证key的唯一性);
6 . 如果bucket满了(超过load factor * current capacity),就要进行resize;
public V put(K key, V value) {
//对key的hashCode()做hash;
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;
//tab为空则创建;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//计算index,并对null做处理;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
//如果节点存在;
if (p.hash == 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) // -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;
//超过load factor * current capacity,进行resize();
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
get()方法
get()方法的实现思路,如下:
1 . bucket里的第一个节点,直接命中;
2 . 如果有冲突,则通过key.equals(key)去查找对应的entry。如果是树,则在树中通过key.equals(key)查找,O(logn);如果是链表,则在链表中通过key.equals(key)查找,O(n);
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) {
//在树中get;
if (first instanceof TreeNode)
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
//在链表中get;
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}
hash()方法
在get和put的过程中,计算下标时,先对hashCode进行hash操作,然后再通过hash值进一步地计算下标;
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
hash()方法的作用是:高16位不变,低16位和高16位做一个异或;
resize()方法
当使用put()时,如果发现目前的bucket占用程度已经超过了Load Factor所希望的比例,那么就会发生resize。resize的过程,简单地说就是把bucket扩充为2倍,之后重新计算index,把节点再放到新的bucket中。
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
----------------------last line for now----------------------