目录
前言
上一期详细介绍了HashMap的底层 的数据结构,源码中的几个重要的属性,构造方法,核心方法put 方法和resize扩容方法。详情请查看 HashMap源码解析(上) 。本期主要结合源码介绍一下剩余的几个常用方法:get方法,remove方法,replace方法。
get方法源码解析
返回哈希表中key对应的value,如果此映射不包含键的映射,则返回null 。 如果此映射包含从键k到值
v的映射,使得 (key==null ? k==null : key.equals(k)) , 则此方法返回v ; 否则返回
null 。 (最多可以有一个这样的映射。) 返回值null不一定表示映射不包含键的映射;
Map也有可能将键显式映射到null 。 containsKey(方法)操作可用于区分这两种情况。
返回哈希表中键对应的值。
下边结合源码来看一下具体的实现:
/**
* 返回哈希表中key对应的value,如果此映射不包含键的映射,则返回null 。
*
* 如果此映射包含从键k到值v的映射,使得
* (key==null ? k==null : key.equals(k)) ,
* 则此方法返回v ; 否则返回null 。 (最多可以有一个这样的映射。)
*
* 返回值null不一定表示映射不包含键的映射;
* Map也有可能将键显式映射到null 。
* containsKey(方法)操作可用于区分这两种情况。返回哈希表中键对应的值
*
*
*/
public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
其实核心的方法还是 getNode:
final Node<K,V> getNode(int hash, Object key) {
// tab 引用当前HashMap的散列表
// first 桶位中的头元素
// e 临时存储的元素
// n 哈希表数组的长度
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
// 如果哈希表不是空,并且长度大于0,表示哈希表已经初始化,或者已经有元素了,
// (n - 1) & hash是计算当前key桶位的位置下标算法,first即当前key所在桶位的头元素
// 如果头元素不是空的表示此桶位上有元素,再去进行取值操作
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
// 最简单的情况:定位出来的头元素就是key所处的元素,直接返回此元素
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
// 头元素不是key所在的元素,first.next != null表示当前桶位上不止一个元素,可能是链表或者红黑树
if ((e = first.next) != null) {
// 红黑树
if (first instanceof TreeNode)
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
// 链表结构
do {
// 如果hash和key和节点的key一致则表示找到了对应的元素
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null); // 表示还有下一个节点,如果没有下一个节点,表示没有找到对应的key
}
}
return null;
}
remove方法源码解析
public V remove(Object key) {
Node<K,V> e;
return (e = removeNode(hash(key), key, null, false, true)) == null ?
null : e.value;
}
remove方法也是通过调用一个核心的方法 removeNode 来做的
/**
* Implements Map.remove and related methods.
*
* @param hash hash for key
* @param key the key
* @param value matchValue为true的情况下,key对应的value和此参数相等才会移除元素,否则忽略
* @param matchValue 如果为真,则仅在值相等时删除
* @param movable 如果为 false,则在删除时不移动其他节点
* @return key所在的Node节点,如果没有,则为 null
*/
final Node<K,V> removeNode(int hash, Object key, Object value,
boolean matchValue, boolean movable) {
// tab 引用当前HashMap的散列表
// p 传入key对应桶位的头元素
// n 当前哈希表数组的长度
// index key元素所在桶位下标
Node<K,V>[] tab; Node<K,V> p; int n, index;
// 哈希表不是空,且长度大于0,key所在的桶位元素不是空
if ((tab = table) != null && (n = tab.length) > 0 &&
(p = tab[index = (n - 1) & hash]) != null) {
// node 传入key所在的节点
// e 临时存储的节点
Node<K,V> node = null, e; K k; V v;
// 第一种情况:key所在桶位的头元素就是key所在的Node节点,赋值给node
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
node = p;
// 如果当前桶位不止一个元素,但不确定是链表结构还是红黑树结构
else if ((e = p.next) != null) {
// 红黑树
if (p instanceof TreeNode)
node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
// 链表
else {
do {
// 链表上找到了key所在的节点,结束循环
if (e.hash == hash &&
((k = e.key) == key ||
(key != null && key.equals(k)))) {
node = e;
break;
}
// 此处保持p的下一个元素一直是e,每循环一次,p,e同时指向各自下一个元素,此时p.next=node
p = e;
} while ((e = e.next) != null);
}
}
// 找到了key所在的节点后,如果节点不是null,说明按照key找到了需要删除的元素
if (node != null && (!matchValue || (v = node.value) == value ||
(value != null && value.equals(v)))) {
// 红黑树
if (node instanceof TreeNode)
((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
// 此时的key所在的节点在头元素,
// 则删除的就是头结点,将头元素的下一个节点作为第一个元素(不管是不是null)存储在对应的桶位
else if (node == p)
tab[index] = node.next;
// key所在元素不是第一个元素,此时的p.next = node,要删除的节点是node,
// 所以p的下一个元素不是指向node,而是node的下一个元素,即p.next = node.next
else
p.next = node.next;
// 增加哈希表结构变化的 次数
++modCount;
// 减少一个哈希表中的元素个数
--size;
afterNodeRemoval(node);
return node;
}
}
return null;
}
repalce方法源码解析
replace方法就很简单的,直接上代码:
public V replace(K key, V value) {
Node<K,V> e;
// 获取key所在的节点,不为空进行value替换
if ((e = getNode(hash(key), key)) != null) {
// 旧值
V oldValue = e.value;
// 将新值赋给key所在节点的value
e.value = value;
afterNodeAccess(e);
// 返回旧值
return oldValue;
}
return null;
}
\