HashMap使用key的hashCode()和equals()方法来将值划分到不同的桶里。桶的数量通常要比map中的记录的数量要稍大,这样 每个桶包括的值会比较少(最好是一个)。当通过key进行查找时,我们可以在常数时间内迅速定位到某个桶(使用hashCode()对桶的数量进行取模) 以及要找的对象。
这些东西你应该都已经知道了。你可能还知道哈希碰撞会对hashMap的性能带来灾难性的影响。如果多个hashCode()的值落到同一个桶内的 时候,这些值是存储到一个链表中的。最坏的情况下,所有的key都映射到同一个桶中,这样hashmap就退化成了一个链表——查找时间从O(1)到 O(n)。
当然这是在jdk8以前,JDK1.6中HashMap采用的是位桶+链表的方式,即我们常说的散列链表的方式,而JDK1.8中采用的是位桶+链表/红黑树的方式,也是非线程安全的。当某个位桶的链表的长度达到某个阀值的时候,这个链表就将转换成红黑树。
看下面的代码
[java]view plaincopy
//链表节点
staticclassNodeimplementsMap.Entry {
finalinthash;
finalK key;
V value;
Node next;
//省略
}
//红黑树节点
staticfinalclassTreeNodeextendsLinkedHashMap.Entry {
TreeNode parent;// red-black tree links
TreeNode left;
TreeNode right;
TreeNode prev;// needed to unlink next upon deletion
booleanred;
TreeNode(inthash, K key, V val, Node next) {
super(hash, key, val, next);
}
//省略
}
// HashMap的主要属性
publicclassHashMapextendsAbstractMap
implementsMap, Cloneable, Serializable {
// 槽数组,Node类型,TreeNode extends LinkedHashMap.Entry,所以可以存放TreeNode来实现Tree bins
transientNode[] table;
transientSet> entrySet;
transientintsize;
// 去掉了volatile的修饰符
transientintmodCount;
intthreshold;
finalfloatloadFactor;
...
}
[java]view plaincopy
//计算key的hash
staticfinalinthash(Object key) {
inth;
return(key ==null) ?0: (h = key.hashCode()) ^ (h >>>16);
}
饭后我们在看看具体的put和get方法
[java]view plaincopy
publicV get(Object key) {
Node e;
return(e = getNode(hash(key), key)) ==null?null: e.value;
}
finalNode getNode(inthash, Object key) {
Node[] tab;
Node first, e;
intn; K k;
//hash & length-1 定位数组下标
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))))
returnfirst;
if((e = first.next) !=null) {
/*第一个节点是TreeNode,则采用位桶+红黑树结构,
* 调用TreeNode.getTreeNode(hash,key),
*遍历红黑树,得到节点的value
*/
if(firstinstanceofTreeNode)
return((TreeNode)first).getTreeNode(hash, key);
do{
if(e.hash == hash &&
((k = e.key) == key || (key !=null&& key.equals(k))))
returne;
}while((e = e.next) !=null);
}
}
returnnull;
}
finalTreeNode getTreeNode(inth, Object k) {
//找到红黑树的根节点并遍历红黑树
return((parent !=null) ? root() :this).find(h, k,null);
}
/*
*通过hash值的比较,递归的去遍历红黑树,这里要提的是compareableClassFor(Class k)这个函数的作用,在某些时候
*如果红黑树节点的元素are of the same "class C implements Comparable" type
*利用他们的compareTo()方法来比较大小,这里需要通过反射机制来check他们到底是不是属于同一个类,是不是具有可比较性.
*/
finalTreeNode find(inth, Object k, Class kc) {
TreeNode p =this;
do{
intph, dir; K pk;
TreeNode pl = p.left, pr = p.right, q;
if((ph = p.hash) > h)
p = pl;
elseif(ph < h)
p = pr;
elseif((pk = p.key) == k || (k !=null&& k.equals(pk)))
returnp;
elseif(pl ==null)
p = pr;
elseif(pr ==null)
p = pl;
elseif((kc !=null||
(kc = comparableClassFor(k)) !=null) &&
(dir = compareComparables(kc, k, pk)) !=0)
p = (dir <0) ? pl : pr;
elseif((q = pr.find(h, k, kc)) !=null)
returnq;
else
p = pl;
}while(p !=null);
returnnull;
}
[java]view plaincopy
//put(K key,V value)函数
publicV put(K key, V value) {
returnputVal(hash(key), key, value,false,true);
}
finalV putVal(inthash, K key, V value,booleanonlyIfAbsent,
booleanevict) {
Node[] tab;
Node p;
intn, i;
//如果table为空或者长度为0,则resize()
if((tab = table) ==null|| (n = tab.length) ==0)
n = (tab = resize()).length;
//找到key值对应的槽并且是第一个,直接加入
if((p = tab[i = (n -1) & hash]) ==null)
tab[i] = newNode(hash, key, value,null);
else{
Node e;
K k;
//第一个node的hash值即为要加入元素的hash
if(p.hash == hash &&
((k = p.key) == key || (key !=null&& key.equals(k)))){
e = p;
}elseif(pinstanceofTreeNode)//第一个节点是TreeNode,即tree-bin
/*Tree version of putVal.
*final TreeNode putTreeVal(HashMap map, Node[] tab,int h, K k, V v)
*/
e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value);
else{
//不是TreeNode,即为链表,遍历链表
for(intbinCount =0; ; ++binCount) {
/*到达链表的尾端也没有找到key值相同的节点,
*则生成一个新的Node,并且判断链表的节点个数是不是到达转换成红黑树的上界
*达到,则转换成红黑树
*/
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);
//返回旧的value值
returnoldValue;
}
}
++modCount;
if(++size > threshold)
resize();
afterNodeInsertion(evict);
returnnull;
}
HashMap会动态的使用一个专门的treemap实现来替换掉它。这样做的结果会更好,是O(logn),而不是糟糕的O(n)。它是如何工作 的?前面产生冲突的那些KEY对应的记录只是简单的追加到一个链表后面,这些记录只能通过遍历来进行查找。但是超过这个阈值后HashMap开始将列表升 级成一个二叉树,使用哈希值作为树的分支变量,如果两个哈希值不等,但指向同一个桶的话,较大的那个会插入到右子树里
个性能提升有什么用处?比方说恶意的程序,如果它知道我们用的是哈希算法,它可能会发送大量的请求,导致产生严重的哈希碰撞。然后不停的访问这些 key就能显著的影响服务器的性能,这样就形成了一次拒绝服务攻击(DoS)。JDK 8中从O(n)到O(logn)的飞跃,可以有效地防止类似的攻击,同时也让HashMap性能的可预测性稍微增强了一些。
转载请注明出处http://blog.csdn.NET/a837199685