HashMap是我们工作中最常用到的。对于put和get方法详细介绍。
get方法:
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
//定位到hash所对应的桶,放到第一个
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
//判断hash值和key是否相等,如果相等直接返回
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;
}
树结构的查询代码:
输查找的主要逻辑是:
在查找时,我们先将被查找的键和子数组的中间键比较。如果被查找的键小于中间键,我们就在左子数组中继续查找,如果大于我们就在右子数组中继续查找,否则中间键就是我们要找的键。
final TreeNode<K,V> find(int h, Object k, Class<?> kc) {
TreeNode<K,V> p = this;
do {
int ph, dir; K pk;
TreeNode<K,V> pl = p.left, pr = p.right, q;
if ((ph = p.hash) > h)
p = pl;
else if (ph < h)
p = pr;
else if ((pk = p.key) == k || (k != null && k.equals(pk)))
return p;
else if (pl == null)
p = pr;
else if (pr == null)
p = pl;
else if ((kc != null ||
(kc = comparableClassFor(k)) != null) &&
(dir = compareComparables(kc, k, pk)) != 0)
p = (dir < 0) ? pl : pr;
else if ((q = pr.find(h, k, kc)) != null)
return q;
else
p = pl;
} while (p != null);
return null;
}
put方法介绍:
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)
//检查table是否为空,如果为空,调用resize()来重新分配空间
n = (tab = resize()).length;
//使用散列算法来确认应该将键值对放在那个桶中,如果为空,创建一个新的节点并放在该桶中
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
//如果桶的哈希值和键的hash值相等,并且桶的键和key相等
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;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
树的节点插入源码实现:
final HashMap.TreeNode<K,V> putTreeVal(HashMap<K,V> map, HashMap.Node<K,V>[] tab, int h, K k, V v) { Class<?> kc = null; boolean searched = false;//标记是否已经进行了搜索 //定义树的根节点,如果父节点不为空,则调用root方法获取父节点,否则,当前节点就是根节点 HashMap.TreeNode<K,V> root = (parent != null) ? root() : this; for (HashMap.TreeNode<K,V> p = root;;) { int dir, ph; K pk; /** * 首先判断当前节点的哈希值与要插入的键的哈希值的大小关系,然后比较当前节点的键和要插入的键是否相同。如果相同,则直接返回当前节点 * * 如果当前节点的键和要插入的键不同,且当前节点的键和要插入的键不可比较,则进行排序并继续搜索。如果仍然找不到合适的位置,则将新节点插入到树中并返回null。 */ if ((ph = p.hash) > h) dir = -1; else if (ph < h) dir = 1; else if ((pk = p.key) == k || (k != null && k.equals(pk))) return p; else if ((kc == null && (kc = comparableClassFor(k)) == null) || (dir = compareComparables(kc, k, pk)) == 0) { if (!searched) { HashMap.TreeNode<K,V> q, ch; searched = true; if (((ch = p.left) != null && (q = ch.find(h, k, kc)) != null) || ((ch = p.right) != null && (q = ch.find(h, k, kc)) != null)) return q; } dir = tieBreakOrder(k, pk); } HashMap.TreeNode<K,V> xp = p; if ((p = (dir <= 0) ? p.left : p.right) == null) { HashMap.Node<K,V> xpn = xp.next; //创建新的树节点并赋值给x HashMap.TreeNode<K,V> x = map.newTreeNode(h, k, v, xpn); //根据dir的值将新节点插入到合适的子节点位置 if (dir <= 0) xp.left = x; else xp.right = x; //设置新节点的相邻节点,和父节点 xp.next = x; x.parent = x.prev = xp; if (xpn != null) ((HashMap.TreeNode<K,V>)xpn).prev = x; //调整树的节点以保持树的平衡 moveRootToFront(tab, balanceInsertion(root, x)); return null; } } }