HashMap的get,containKey, containsValue方法
前面我们基本把put方法的过程解析完了,这一篇我们看一下与查询有关的方法。
get 方法
下面我们先看一下通过key获取value的方法get。
public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
这里可以看到,其重要的就是如何获取传入的key所对应的节点,一旦我们获取到了节点,就可以拿到值了,我们看一下getNode方法
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 && ((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;
}
上面代码就是获取节点的方法,我们记下来一步一步看。
if ((tab = table) != null && (n = tab.length) > 0 && (first = tab[(n - 1) & hash]) != null)
首先要保证数组已经创建出来,不能实例化HashMap就从中get数据,肯定是null。并且此时的table的长度要大于0. 然后通过hash和容量的与运算计算节点的桶位(如何计算的,请看上一节put)。此时要保证计算到的位置上的节点不为null,否则就是不存在。
当上述条件成立之后,我们看是分为三种情况获取:
- ① 数组上的节点就是要查询的节点
- ② 需要遍历链表看查询节点
- ③ 需要查询红黑树
第一种:数组上
if (first.hash == hash && ((k = first.key) == key || (key != null && key.equals(k))))
这个判断我们在put的时候也见过。需要保证传入的hash与节点的hash值一致,同时保证key是相同的。
第二种:红黑树
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
红黑树的查找不做深究,但是我们进去看一下,也基本可以看懂,其实就是通过hash值的判断来选取查询左子树或是右子树。
第三种:链表
do {
//链表
if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
链表的查询其实基本和第一种一样,只要掌握了链表的结构特点,其实是比较简单的。
到这里,get方法其实就分析完了
containsKey 方法
public boolean containsKey(Object key) {
return getNode(hash(key), key) != null;
}
我们看完上面的get方法之后,明白了getNode方法,再看containsKey就比较容易了。其实就是看getNode查询到的节点是不是null,就可以判断这个key是不是存在了。
containsValue 方法
public boolean containsValue(Object value) {
Node<K,V>[] tab; V v;
if ((tab = table) != null && size > 0) {
for (int i = 0; i < tab.length; ++i) {
//可以看到,这里是使用链表的方式来查询是否包含这个value。
for (Node<K,V> e = tab[i]; e != null; e = e.next) {
if ((v = e.value) == value || (value != null && value.equals(v)))
return true;
}
}
}
return false;
}
查询指定值是否存在就和判断key是否存在不同了,因为key是可以通过计算hash值来查询的,这样就可以将查询效率进行优化,但是查询值的话只能通过一个一个遍历的方式来查询。
我们通过前面的put方法可以知道,虽然当链表长度超过8的时候,会将链表转为红黑树,但是其红黑树结构依旧维护了双向链表的结构,所以当我们无法通过key去查找的时候,我们可以将其统一当前链表来查询【这种方式效率低于正常key查询】。
这样,我们再回头看上面的代码就可以知道了,外层循环遍历table数组,内层循环遍历链表数据,一旦查找到相同的value就返回true,否则就返回false。