上篇文章由于篇幅有限,只讲了put方法以及扩容resize()方法。下面接着讲get以及remove方法。上文的链接:http://blog.csdn.net/zhujiangtaotaise/article/details/79046604
我们还是用上次的数据,即有3个数据,3个数据的key分别为 m,n,t,对应的hash值分别为5,6,9。如下图:
一 、get方法 :
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) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
//如果tab不为null且tab中有数据且tab相应索引的位置上的数据不为null
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
//如果key的hash值相等且key也相等,那么直接返回这个相应的值
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
//如果上面的条件不满足且first的next指向的数据不为null
if ((e = first.next) != null) {
//如果first是红黑树类型,则以红黑树的方式查找该节点
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 key为t的数据:
第3行: if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n – 1) & hash]) != null) //table中有3个数据,所以tab不为null,n是tab的长度,等于4,(n – 1) & hash = 0011&1001=1;由于tab[1]不为null,所以if条件成立,first指向tab[1],如下图:
第5行:if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
由上图可知first指向数据的hash值为0101,而我们想要get的数据的key为t,hash值为1001,所以if条件不满足,走到第8行。
第8行:if ((e = first.next) != null) // e = first.next,即e指向first为头结点的链表的下一个结点,如下图:
可知if条件成立,走到第9行,判断 first 是否是红黑树类型,显示是单链表类型,所以条件不成立,走到第11行的do…while
第12行:if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
由上图可知,e的hash值等于1001等于我们要get的数据的hash值,并且key值相等,所以条件成立,返回 key为t的数据。循环结束,如果还没找到数据的话就接着遍历该链表的下一个数据,直到找到该数据。
到这里,get的方法讲完了,我来写个我之前遇到的例子,大家运行的结果是啥?
Map<Long, String> map1 = new HashMap<Long, String>();
map1.put(1L, "abc");
System.out.println(" map1 val = " + map1.get(1));
可能有的人直到答案是null了,有些人可能还不知道,一起来看看啊。
我们先来打印几个值看看:
Long a1 = 1l;
Integer a2 = 1;
System.out.println("a1.hashCode = " + a1.hashCode()+", a2.hashCode = "+a2.hashCode());
System.out.println("a1.equals(a2) = "+a1.equals(a2));
结果如下:
a1.hashCode = 1, a2.hashCode = 1
a1.equals(a2) = false
我们put进去的key为Long类型,而get的时候用的却是Integer类型,从打印的结果来看,a1是Long型,a2是Integer类型,a1和a2的hashcode 是相等的,那么通过hash(key)方法获取的hash值肯定也是相等的。但是a1和a2通过equals判断的结果是false,且 由于传递进去后的key都是Object类型,1L == 1 也是false, 可以看到getNode方法的第5行;
第5行:if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k)))) //从上面的分析可知((k = first.key) == key || (key != null && key.equals(k))是不成立的,所以返回的是null。
二、remove方法
代码如下:
public V remove(Object key) {
Node<K,V> e;
//如果找到要remove的数据则返回该数据的value,否则返回null
return (e = removeNode(hash(key), key, null, false, true)) == null ?
null : e.value;
}
主要看看里面的removeNode方法:
final Node<K,V> removeNode(int hash, Object key, Object value,
boolean matchValue, boolean movable) {
Node<K,V>[] tab; Node<K,V> p; int n, index;
//如果tab不为null且tab中有数据且tab相应索引的位置上的数据不为null
if ((tab = table) != null && (n = tab.length) > 0 &&
(p = tab[index = (n - 1) & hash]) != null) {
Node<K,V> node = null, e; K k; V v;
//如果key的hash值相等且key也相等,那么将p赋值给node
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
node = p;
//如果条件不成立,如果p是红黑树类型则通过红黑树的方式找到 node,
else if ((e = p.next) != null) {
if (p instanceof TreeNode)
node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
//否则以遍历链表的方式找到该node
else {
do {
if (e.hash == hash &&
((k = e.key) == key ||
(key != null && key.equals(k)))) {
node = e;
break;
}
p = e;
} while ((e = e.next) != null);
}
}
//找到待删除的结点后,执行的链表删除操作,等下会有实例分析
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);
else if (node == p)
tab[index] = node.next;
else
p.next = node.next;
++modCount;
--size;
//这个方法这hashmap的子类linkhashmap中实现了,在hashmap里是一个空方法
afterNodeRemoval(node);
return node;
}
}
return null;
}
原始数据还是之前的3个,假设我想删除的数据是key为m,hash值为0101的数据。
看removeNode的第4行代码;
第4行:if ((tab = table) != null && (n = tab.length) > 0 &&
(p = tab[index = (n – 1) & hash]) != null) //table中有3个数据,table的长度为4,index = (n – 1) & hash = 0011&0101=1,table[1]中存放的是key为m的数据,所以
if条件成立,p指向table[1],如下图:
接下来走到第6行,
第6行: if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k)))) // 由上图可知p的hash值为0101,key为m,是我们想要删除的数据,所以该条件成立。走到第8行;
第8行:node = p; //node 指向结点p,如下图:
第21行: if (node != null && (!matchValue || (v = node.value) == value ||
(value != null && value.equals(v)))) // 由上图可知node指向table[1],而matchValue 是false,后面的条件可不看了,if条件成立。
第22行:if (node instanceof TreeNode) //不成立,
第24行:else if (node == p) //由上图可知 node == p成立,
第25行:tab[index] = node.next; //index上面分析了等于1,而node.next指向key为t的数据。即tab[1]存放key为t的数据,如下图:
假设我们删除的是key为 t 的数据,table中还是之前的3个数据。
前面几行的逻辑如上,但第6行条件却不成立了,
第6行: if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k)))) //此是p指向的是table[1],p的hash= 0101,而我们要删除的数据key等于t,hash值等于1001,所以if条件不成立。走到第9行;
第9行: else if ((e = p.next) != null) //e=p.next p指向table[1]的第一个数据,p.next指向的是key为t的数据,所以 e 指向key为t的数据,如下图:
该if条件成立,第10行p是否是红黑树类型,不满足;走到12行执行do…while操作,如下:
第14行 : if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) // e指向的结点的key为t,hash值为1001就是我们要删除的数据,条件满足,走到下一行:
node = e; //node 结点执行e,如下图:
然后执行break;跳出循环。
接着执行21行的if语句,上面分析了,该if成立,由于 node 不是红黑树类型的结点,所以走到24行:
第24行:else if (node == p) //上图可知,node不等于p,走到下一行
第26行:p.next = node.next; //node后面没有数据,所以node.next指向为空,即将p结点的next指向为空,就变成下图的结果:
最后返回被删除的结点 node。
HashMap的主要操作就是这样的了,后续会介绍LinkHashMap,里面涉及到双向的循环链表,这个得有一定的数据结构基础才行,不然可能会有点懵逼,可能我写的不大好,欢迎指正。