范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文
国学影视

HashMap底层实现原理

  HashMap采用Node 数组来存储key-value对,每一个键值对组成了一个Node实体,Node类实际上是一个单向的链表结 构,它具有Next指针,可以连接下一个Node实体。HashMap在JDK1.8之前和之后的区别
  JDK1.8 之前,数组 + 链表 存储结构
  缺点就是哈希函数很难使元素百分百的均匀分布,这会产生一种极端的可能,就是大量的元素存在一个桶里
  JDK1.8 之后:数组 + 链表 + 红黑树添加元素时:链表长度 大于8的时候,转换为红黑树删除元素、扩容时,同上,数量大于8时,也是采用红黑树形式存储,但是在数量较少时,即数量小于6时,会将红黑树转换回链表 遍历、查找时,使用红黑树,他的时间复杂度O(log n),便于性能的提高。 数据结构
  存储结构
  static class Node implements Map.Entry {         final int hash;         final K key;         V value;         Node next;          Node(int hash, K key, V value, Node next) {             this.hash = hash;             this.key = key;             this.value = value;             this.next = next;         }          public final K getKey()        { return key; }         public final V getValue()      { return value; }         public final String toString() { return key + "=" + value; }          public final int hashCode() {             return Objects.hashCode(key) ^ Objects.hashCode(value);         }          public final V setValue(V newValue) {             V oldValue = value;             value = newValue;             return oldValue;         }          public final boolean equals(Object o) {             if (o == this)                 return true;             if (o instanceof Map.Entry) {                 Map.Entry<?,?> e = (Map.Entry<?,?>)o;                 if (Objects.equals(key, e.getKey()) &&                     Objects.equals(value, e.getValue()))                     return true;             }             return false;         }     }
  // Node 数组  transient Node[] table;  //加载因子 final float loadFactor;  //默认加载因子 ,容量达到这个比例时自动扩容 static final float DEFAULT_LOAD_FACTOR = 0.75f;  // 数量大于8时,链表转换为红黑树形式存储  static final int TREEIFY_THRESHOLD = 8;  //即数量小于6时,会将红黑树转换回链表,删除元素时remove static final int UNTREEIFY_THRESHOLD = 6; //PUT 时每次都做hash public V put(K key, V value) {     return putVal(hash(key), key, value, false, true); } //put  函数核心算法 final V putVal(int hash, K key, V value, boolean onlyIfAbsent,                    boolean evict) {         Node[] tab; Node p; int n, i;         if ((tab = table) == null || (n = tab.length) == 0)             n = (tab = resize()).length;         if ((p = tab[i = (n - 1) & hash]) == null) // 这里的n 表示数组的长度。              // hash              tab[i] = newNode(hash, key, value, null);         else {             Node e; K k;             if (p.hash == hash &&                 ((k = p.key) == key || (key != null && key.equals(k))))                 e = p;             else if (p instanceof TreeNode)                 e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value);             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;                 }             }             if (e != null) { // existing mapping for key                 V oldValue = e.value;                 if (!onlyIfAbsent || oldValue == null)                     e.value = value;                 afterNodeAccess(e); // 空实现                 return oldValue;             }         }         ++modCount;   // modCount 是java集合中Fail-Fast的底层实现原理         if (++size > threshold) //扩容             resize();         afterNodeInsertion(evict);// 空实现         return null;     }          // Callbacks to allow LinkedHashMap post-actions     void afterNodeAccess(Node p) { }     void afterNodeInsertion(boolean evict) { }     void afterNodeRemoval(Node p) { }思考
  java集合中的快速失败:modCount
  // 快速失败是Java集合的一种错误检测机制,当多个线程对集合进行结构上的改变的操作时,有可能会产生fail-fast。
  //举个例子:假设存在两个线程(线程1、线程2),线程1通过Iterator在遍历集合A中的元素,在某个时候线程2修改了集合A的结构(是结构上面的修改,而不是简单的修改集合元素的内容),那么这个时候程序就可能会抛出 ConcurrentModificationException异常,从而产生fast-fail快速失败。
  HashMap中遍历算法如下:final class KeySet extends AbstractSet {     public final int size()                 { return size; }     public final void clear()               { HashMap.this.clear(); }     public final Iterator iterator()     { return new KeyIterator(); }     public final boolean contains(Object o) { return containsKey(o); }     public final boolean remove(Object key) {         return removeNode(hash(key), key, null, false, true) != null;     }     public final Spliterator spliterator() {         return new KeySpliterator<>(HashMap.this, 0, -1, 0, 0);     }     public final void forEach(Consumer<? super K> action) {         Node[] tab;         if (action == null)             throw new NullPointerException();         if (size > 0 && (tab = table) != null) {             int mc = modCount;             for (int i = 0; i < tab.length; ++i) {                 for (Node e = tab[i]; e != null; e = e.next)                     action.accept(e.key);             }             if (modCount != mc)               //抛出异常,Fail-Fast                 throw new ConcurrentModificationException();          }     } }优化hash 算法
  JDK1.8中,是通过hashCode()的高16位异或低16位实现的:(h=k.hashCode())^(h>>>16),主要是从速度,功效和质量来考虑的,减少系统的开销,也不会造成因为高位没有参与下标的计算,从而引起的碰撞。 //扰动函数:促使元素位置分布均匀,减少碰撞几率 static final int hash(Object key) {         int h;   	    // 如果key == null  返回的值是 0          // 如果key !=null           // 首先计算 key 的 hashcode 值赋值给 h ,         // 然后与h 无符号右移16位后的二进制按位异或预算得到最后hash值         return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }	hash具体实现过程如下图所示:
  hash 计算过程与putval函数的应用key.hashCode();返回散列值也就是hashcode,假设随便生成的一个值。 n表示数组初始化的长度是16。 &(按位与运算):运算规则:相同的二进制数位上,都是1的时候,结果为1,否则为零。 ^(按位异或运算):运算规则:相同的二进制数位上,数字相同,结果为0,不同为1。
  高16bit不变,低16bit和高16bit做了一个异或(得到的hashCode转化为32位二进制,前16位和后16位低16bit和高16bit做了一个异或) 为什么这样实现呢?
  如果当n即数组长度很小,假设是16的话,那么n - 1即为1111 ,这样的值和hashCode直接做按位与操作,实际上只使用了哈希值的后4位。如果当哈希值的高位变化很大,低位变化很小,这样就很容易造成哈希冲突了,所以这里把高低位都利用起来,从而解决了这个问题。降低了Hash冲突的概率为什么要用异或运算符
  保证了对象的hashCode的32位值只要有一位发生改变,整个hash()返回值就会改变。尽可能的减少碰撞。
  工作原理
  存储对象时,将K/V键值传给put()方法:调用hash(K)方法计算K的hash值,然后结合数组长度,计算得数组下标;调整数组大小(当容器中的元素个数大于capacity*loadfactor时,容器会进行扩容resize为2n);hash碰撞
  i.如果K的hash值在HashMap中不存在,则执行插入,若存在,则发生碰撞;
  ii.如果K的hash值在HashMap中存在,且它们两者equals返回true,则更新键值对;
  iii.如果K的hash值在HashMap中存在,且它们两者equals返回false,则插入链表的尾部(尾插法)或者红黑树中(树的添加方式)。(JDK1.7之前使用头插法、JDK1.8使用尾插法)
  //get 实现 public V get(Object key) {     Node e;     return (e = getNode(hash(key), key)) == null ? null : e.value; }  /**  * Implements Map.get and related methods  *  * @param hash hash for key  * @param key the key  * @return the node, or null if none  */ final Node getNode(int hash, Object key) {     Node[] tab; Node first, e; int n; K k;     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))))             return first;         if ((e = first.next) != null) {             if (first instanceof TreeNode)                 return ((TreeNode)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; }
  问题思考?
  Q:默认初始化大小是多少?为啥是这么多?为啥大小都是2的幂?
  A:hash运算的过程其实就是对目标元素的Key进行hashcode,再对Map的容量进行取模,而JDK 的工程师为了提升取模的效率,使用位运算代替了取模运算,这就要求Map的容量一定得是2的幂。HashMap的容量为什么是2的n次幂,和这个putval方法中(n - 1) & hash的计算方法有着千丝万缕的关系,符号&是按位与的计算,这是位运算,计算机能直接运算,特别高效,按位与&的计算方法是,只有当对应位置的数据都为1时,运算结果也为1,当HashMap的容量是2的n次幂时,(n-1)的2进制也就是1111111***111这样形式的,这样与添加元素的hash值进行位运算时,能够(充分的散列),使得添加的元素均匀分布在HashMap的每个位置上,减少hash碰撞。
  Q:HashMap如何有效减少碰撞?
  A: 扰动函数:促使元素位置分布均匀,减少碰撞几率 、使用final对象,并采用合适的equals()和hashCode()方法

每月投资700元,退休可以拿到400万元吗?一个家庭,增加财富有两种途径一种途径是通过努力工作储蓄财富另一种途径是通过理财积聚财富。实际上,理财给家庭增加财富的重要性,远远大于单纯地通过工作赚钱。如果每个月你有节余700元,政策助力光伏产业驶入高质量发展快车道记者观察本报记者夏金彪近日,国家能源局发布关于公开征求光伏电站开发建设管理办法(二次征求意见稿)意见的通知。其根据光伏行业面临的新形势新任务新要求,进一步规范光伏电站开发建设管理,王健林从负债4000亿到盈利1200亿躲过危机让人佩服或许是上天的眷恋,让万达和王健林躲过一劫!但却没躲过一个败家的儿子。王健林1954出生于四川绵阳,父亲是老红军。1970年入伍,干到团长转业到大连任职。在1989年进入房地产行业,印尼总统接受英媒采访透露将考虑从俄罗斯购买石油据英国金融时报12日报道,印尼总统佐科在接受该媒体采访时表示,印尼将考虑从俄罗斯购买石油,以减缓印尼能源成本不断上涨带来的压力。路透社报道截图金融时报称,因燃油价格飙升,印尼考虑购家居行业要加快全产业链的智能转型在本月初举行的2022国际消费季家居焕新消费节上,商务部相关负责人表示,将有效促进消费创新和产业创新双提升,不断激发释放家居消费需求,营造家居消费新热潮。近年来,家居行业发展迅速。收藏股权中隐藏的12个大坑,汇总股东因利益而团结合作,共渡难关股东也会因利益而反目成仇,彼此陷害。股东之间的战争因股权而起,最终也会以股权而终结。本文特意选择实践中,大家会误解的和公司股权有关的纠纷,予以解读。因关注10月1日起,云南省失业保险金标准将上调!日前云南省人力资源和社会保障厅发布关于调整全省失业保险金标准的通知启动最低工资标准与失业保险金标准联动调整机制调整失业保险金发放标准进一步提高失业保障水平切实保障云南省失业人员的基浦发银行被罚933万元9月9日,国家外汇管理局上海分局发布的行政处罚信息显示,上海浦东发展银行股份有限公司(以下简称浦发银行)因违规办理远期结汇业务等5项违法事实,被责令改正警告,并处罚款933万元,没交广夜听未来3年,请做好随时调整的准备听夜晚的声音活下来就有未来来源十点读书作者十点小嗲主播珈宁点击下方音频收听更多精彩内容和细节最近一段,华为又上热搜了。原来,消失了很久的任正非,在华为内部发表的文章刷屏网络。他警醒亚洲货币保卫战持续升级1。2万亿美元外储也救不了日元?随着周一(9月12日)美元兑非美货币纷纷回落,但美元兑日元却继续走高,外汇市场上越来越多投资者正开始把目光放到日本决策者的应对措施之下。因为这很可能将决定着,年内美元升值大背景下所巴菲特并非在看空新能源动力电池企业加快出海步伐内蒙古包头重点打造新能源等产业集群内蒙古包头市人民政府关于促进高新技术产业开发区高质量发展的实施意见发布,其中提到做强特色主导产业集群。坚持围绕产业链部署创新链围绕创新链布局产业链
马龙樱花节后忙碌的小黄人近日,马龙熙熙攘攘的樱花节美食节画上了圆满句号。樱花谷景区的樱花树也抽出了绿芽,赏花的游客也零星散落,美食街经营的商户及小摊贩也开始拆除撤走临时搭建的美食棚和摊位,留下了躺在马路上股价飙涨!业绩显韧性,同程旅行的前景值得期待?3月22日,在线旅游龙头同程旅行(00780。HK)开盘迅速拉升,一度涨逾10。截至收盘,公司股价收涨7。72,报16。74港元股,总市值为375。22亿港元。值得一提的是,若拉长乾隆南巡的是与非乾隆帝在清朝盛世时期南巡,时间分别是乾隆十六年(1751)正月十三日至五月初四日二十二年正月十一日至四月二十六日二十七年正月十二日至五月初四日三十年正月十六日至四月二十一日四十五年中华民国到底有多少军阀?头条创作挑战赛因为中华民国时期,混战兼并不断,所以我们这里取1926年初军阀分布来进行说明。晋军阎锡山(主属地山西)粤军蒋介石(主属地广东)湘军唐生智(主属地湖南)奉军张作霖(主属20年,伤痕从未消退伊拉克战争创伤深刻伊拉克人心头2003年3月20日,美国及其西方盟友以所谓伊拉克有大规模杀伤性武器为由,不顾国际社会强烈反对,悍然发动伊拉克战争。开战前,美国声称将解放伊拉克人民帮伊拉克人建立一个团结稳定和自由太祖和高祖有何区别?为何赵匡胤叫宋太祖,李渊只配叫唐高祖?庙号的由来商是中国第二个朝代,也是中国最早在同一时间内有文字记录的朝代。在封建社会,皇帝不仅仅是一种象征,而且还是一种绝对的权威。皇上的一举一动,都是万众瞩目的焦点。历代皇帝死后,清史杂谈三藩之乱解析三藩之乱的起因经过及影响在阅读此文之前,麻烦您点击一下关注,既方便您进行讨论和分享,又能给您带来不一样的参与感,感谢您的支持。康熙(1662年1722年)是清朝第四位皇帝,其统治时期是中国封建社会的鼎盛时民国时期甘肃植树造林成活率低这个问题是咋解决的?民国时期小西湖植树造林活动资料照片树木有培植风景涵养水源防风固沙保持水土调节气候的作用,对维持自然环境和人类生活至关重要,尤其对于干旱少雨的甘肃地区更是如此。植树造林是一项建设要政民国第一父亲非他莫属,子女个个人中龙凤,非富即贵海外求学出生在海南岛文昌县,原名韩教准,也叫宋耀如韩乔荪。9岁的时候,到爪畦一个亲戚家开的店当学徒。后来投奔美国波士顿堂舅,在其家的丝茶小店做店员。因其没有子女,收当儿子,改名宋嘉松江往事九峰十二山是一本无字的书一hr三亿年前地壳运动结束的时候,上海境内的基底岩面是从松江的九峰十二山这里向东向北倾斜的,在今天长江入海口的长兴岛凤凰镇西侧,基底岩面的最深处在地平面以下400米处。那个时刻,如为何武则天一定要灭掉薛丁山一族?真相被揭开,皆是权谋的博弈武则天是中国历史上唯一的女皇帝,她曾经经历过从普通妃子到皇后再到执掌朝政的历程。她治下的唐朝是中国历史上繁荣昌盛的时期之一,但她也因为在政治上的手段和手段而备受争议。其中一个备受争