首页 > 编程知识 正文

一致性hash和hash槽算法区别,hashmap的hashcode

时间:2023-05-05 13:41:12 阅读:21591 作者:4115

对于HashMap put值,第一个put和第二个put源代码分析第一个put值1;对于第一个put值,以下代码1、put(1.1,hash )、putVal () 2.1 resize () )

HashMap的源代码还得仔细看,了解源代码后一会儿也会忘记。 如果正好有时间,让我们回顾一下向HashMap上传值时的流程。 如果有错误,欢迎cjdbbt的各位指出。

初次put值

如果new为HashMap,则HashMap的数组长度为0,并且只有在首次上传值时,才会为HashMap提供初始化的数组长度。

另一方面,在第一次朝向HashMap put值时,以下代码1、put(publicvput ) key,V value ) returnputval(hash(key ) key,value,false,true ) 判断//key是否为空,如果为空则返回0,否则将key.hashCode (返回,用异或将key.hashCode ) )向右移位16位后的值return ) key==null )? 0:(h=key.hashcode () ) ) ) ) ) ) ) ); } 2、putval(finalvputval ) inthash,K key,V value,boolean onlyIfAbsent,boolean evict ) { NodeK,V[] tab; NodeK,V p; int n,I; 如果确定//tab是否为空,则在数组中包含长度resize ()方法if ) ) tab=table )==null||(n=tab.length )==0(n=) ) tab=resize ) 如果被占用,则进入else,否则直接将key value放入节点if((p=tab[I=(n-1 ) hash] )==null )//new,并将key和value节点放入tab [ I ] ELSE{/************第一个put值时未去的代码已被省略。 *************//修改结构的次数1,modCount是为混列而修改的次数//size1为threshold (数组长度*负载系数的值可以称为阈值) ) //该方法为空,在链接的混图中使用LRU算法。 该方法有内容,实际上是为了扩展其他的关联(evict )。 返回空值; }2.1resize(final{ NodeK,v ) resize ) (nodek,v ) oldtab=table; //如果旧数组是否为null,则长度为0。 否则,old tab.lengthintoldcap=(old tab==null )? 0 : oldTab.length; //将数组长度和负载因子的乘积(阈值)分配给oldThr int oldThr=threshold; int newCap,newThr=0; //第一次的hashMap put值oldCap一定为0,因此该判断将if(oldcap0)…}//oldthr也设为0以下, elseelseif ) oldthr0)/initialcapacitywasplacesplacement else//zeroinitialthresholdsignifiesusingdefaults//DEFAULT_LOAD_FACTOR (负载因子) default _ initor (负载因子),将default_initial_capacity (向左4=16个位移)的值指定给newcapnewcap=defcap

newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); } //已经给newThr赋值,不走此代码 if (newThr == 0) { ... } //将newThr的值赋给阈值 threshold = newThr; @SuppressWarnings({"rawtypes","unchecked"}) //初始化newTab,给newTab一个长度 Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; //将空数组newTab赋给table table = newTab; //oldTab为空不走此代码 if (oldTab != null) { ... } return newTab;} 2.2 newNode( ) // 创建一个node节点Node<K,V> newNode(int hash, K key, V value, Node<K,V> next) { return new Node<>(hash, key, value, next);}

至此,第一次put数据流程走完,HashMap的Node节点长度为16.

二、第二次put值 1、put public V put(K key, V value) { return putVal(hash(key), key, value, false, true);} 1.1、hash() static final int hash(Object key) { int h; //判断key是否为空,如果为空则返回0否则返回key.hashCode()异或上key.hashCode()向右位移16位的值 return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);} 2、putVal final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node<K,V>[] tab; Node<K,V> p; int n, i; //由于第一次put时已将数据放入table中,此时table!=null所以不走 if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; //判断数组长度与上新key的hashcode是否已被占用,如果占用则hash冲突,走else,否则直接存值 if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); else { /***************此时else还不走,当出现hash碰撞时,才会走else代码***************/ } ++modCount; if (++size > threshold) resize(); afterNodeInsertion(evict); return null;} 2.1 newNode( ) // 创建一个node节点Node<K,V> newNode(int hash, K key, V value, Node<K,V> next) { return new Node<>(hash, key, value, next);}

至此第二次put数据流程走完,HashMap中经过两次put已将数据存放至hashmap中,虽然看似两次put所走的代码一样(除了第一次put时resize()了),但是只是没有出现hash冲突的情况,如果两次put的key存在hash冲突,则会将第二次的数据和第一次的数据放入同一个链表中。

三、hash冲突时 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) n = (tab = resize()).length; if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); //当出现hash冲突时tab[i = (n - 1) & hash]!=null,走else else { Node<K,V> e; K k; //老key的hash值==新key的hash并且老key==新key的话会将新value覆盖老value if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; //如果key不相同,那么判断数据结构是否为红黑树,如果为红黑树则直接将新节点插入树中 else if (p instanceof TreeNode) e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); //如果还是链表的话,走for循环,循环的对象是p的next节点不为null或者p的后续节点有key相同,如果key相同,那么找到这个节点,用新值替换。否则,往链表的最后一点节点添加数据,因为遍历找链表,使用binCount来记录遍历个数,当遍历的个数达到门限值(默认是8)达到8个那么就要发生树化改造 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; } } //正如上边所述key相同,则将value替换老的value 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; } 四、总结

用大白话来说:

第一次put值时,先判断节点是否为空,如果为空则resize一个长度,并将key,value放入节点中。第二次put值时,判断是否存在hash冲突,如果未冲突则直接将key,value放入节点中,如果出现hash冲突,则看key是否相同,相同则直接将新value替换旧value,不同则判断数据结构是否为树形结构,是则将节点插入树中,不是则为链表,便遍历链表知道next节点为null时将节点放入链表中,如果此时发现再插入此节点链表长度就大于树化负载因子的话,那么就对链表进行树化。

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。