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

吃透JDK1。8中HashMap的源码和底层数据结构

  1 HashMap 简介
  HashMap   主要用来存放键值对,它基于哈希表的 Map 接口实现,在Java中是常用 集合,它是非线程安全的。
  HashMap  存储的是 key-vlaue   格式数据,需要注意的是其key和value的值都可以为空,但是key值为空的只能有一个,value咋可以有多个空值数据。
  JDK1.8 之前 HashMap   由 【数组+链表  】   组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的("拉链法"解决冲突)。
  JDK1.8 以后的 HashMap   由 【数组+链表+红黑树】   组成的,以此来加快查询速度。使用红黑树的两个条件:
  1、当链表长度大于阈值(默认为 8)时,将链表转化为红黑树。
  2、在转换之前会先判断当前数组的长度,如果当前数组长度小于 64,那么会先对数组扩容,也就是数组长度必须超过64。2 底层数据结构分析2.1 JDK8 之前
  JDK8   之前  HashMap   底层是  【数组和链表】  结合在一起使用也就是 链表散列  。
  HashMap   通过 key 的 hashCode 经过 扰动函数  处理过后得到 hash 值,然后通过  (n - 1) & hash   判断当前元素存放的位置(这里的 n 指的是数组的长度),如果当前位置存在元素的话,就判断该元素与要存入的元素的 hash 值以及 key 是否相同,如果相同的话,直接覆盖,不相同就通过拉链法解决冲突。
  扰动函数  :  HashMap   的  hash()  。使用 hash() 是为了防止实现较差的 hashCode() 方法【换种说法就是用hash()可以减少碰撞】。
  JDK8    HashMap   的  hash()  源码:
  JDK8   的  hash()   相比于  JDK7 hash()   更加简化,但是原理不变。源码:     static final int hash(Object key) {       int h;       // key.hashCode():返回散列值也就是hashcode       // ^ :按位异或       // >>>:无符号右移,忽略符号位,空位都以0补齐       return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);   }Copy to clipboardErrorCopied
  JDK7   的  HashMap   的  hash()   源码: static int hash(int h) {     // This function ensures that hashCodes that differ only by     // constant multiples at each bit position have a bounded     // number of collisions (approximately 8 at default load factor).      h ^= (h >>> 20) ^ (h >>> 12);     return h ^ (h >>> 7) ^ (h >>> 4); }Copy to clipboardErrorCopied
  对比JDK8和JDK7的hash()源码,可以看出jdk7中的hash()扰动了4次,因此其性能相对于jdk8要差一点。
  "拉链法"  :将链表和数组相结合。也就是创建一个链表数组,数组中每一格就是一个链表。若遇到哈希冲突,则将冲突的值加到链表中即可。
  2.2 JDK8 之后
  JDK8   之后在解决哈希冲突时变化很大。
  当链表长度大于阈值(默认为 8)时,会首先调用  treeifyBin()  。这个方法会根据 HashMap 数组来决定是否转换为红黑树。只有当数组长度大于或者等于 64 的情况下,才会执行转换红黑树操作,以减少搜索时间。否则,就是只是执行 resize()   对数组扩容。相关源码这里就不贴了,重点关注 treeifyBin()   即可!
  HashMap  类的属性: public class HashMap extends AbstractMap implements Map, Cloneable, Serializable {     // 序列号     private static final long serialVersionUID = 362498820763181265L;     // 默认的初始容量是16     static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;     // 最大容量     static final int MAXIMUM_CAPACITY = 1 << 30;     // 默认的填充因子     static final float DEFAULT_LOAD_FACTOR = 0.75f;     // 当桶(bucket)上的结点数大于这个值时会转成红黑树     static final int TREEIFY_THRESHOLD = 8;     // 当桶(bucket)上的结点数小于这个值时树转链表     static final int UNTREEIFY_THRESHOLD = 6;     // 桶中结构转化为红黑树对应的table的最小容量     static final int MIN_TREEIFY_CAPACITY = 64;     // 存储元素的数组,总是2的幂次倍     transient Node[] table;     // 存放具体元素的集     transient Set> entrySet;     // 存放元素的个数,注意这个不等于数组的长度。     transient int size;     // 每次扩容和更改map结构的计数器     transient int modCount;     // 临界值(容量*填充因子) 当实际大小超过临界值时,会进行扩容     int threshold;     // 加载因子     final float loadFactor; }Copy to clipboardErrorCopiedloadFactor 加载因子 loadFactor 加载因子是控制数组存放数据的疏密程度,loadFactor 越趋近于 1,那么 数组中存放的数据(entry)也就越多,也就越密,也就是会让链表的长度增加,loadFactor 越小,也就是趋近于 0,数组中存放的数据(entry)也就越少,也就越稀疏。 loadFactor 太大导致查找元素效率低,太小导致数组的利用率低,存放的数据会很分散。loadFactor 的默认值为 0.75f 是官方给出的一个比较好的临界值 。给定的默认容量为 16,负载因子为 0.75。Map 在使用过程中不断的往里面存放数据,当数量达到了 16 * 0.75 = 12 就需要将当前 16 的容量进行扩容,而扩容这个过程涉及到 rehash、复制数据等操作,所以非常消耗性能。 threshold threshold = capacity * loadFactor ,当 Size>=threshold 的时候,那么就要考虑对数组的扩增了,也就是说,这个的意思就是 衡量数组是否需要扩增的一个标准 。
  Node   节点类源码: // 继承自 Map.Entry static class Node implements Map.Entry {        final int hash;// 哈希值,存放元素到hashmap中时用来与其他元素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; }         // 重写hashCode()方法         public final int hashCode() {             return Objects.hashCode(key) ^ Objects.hashCode(value);         }          public final V setValue(V newValue) {             V oldValue = value;             value = newValue;             return oldValue;         }         // 重写 equals() 方法         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;         } }Copy to clipboardErrorCopied
  TreeNode  类源码: static final class TreeNode extends LinkedHashMap.Entry {         TreeNode parent;  // 父         TreeNode left;    // 左         TreeNode right;   // 右         TreeNode prev;    // needed to unlink next upon deletion         boolean red;           // 判断颜色         TreeNode(int hash, K key, V val, Node next) {             super(hash, key, val, next);         }         // 返回根节点         final TreeNode root() {             for (TreeNode r = this, p;;) {                 if ((p = r.parent) == null)                     return r;                 r = p;        }Copy to clipboardErrorCopied3 HashMap 源码分析3.1 构造方法源码分析
  HashMap   中有四个构造方法源码分析:     // 默认构造函数。     public HashMap() {         this.loadFactor = DEFAULT_LOAD_FACTOR; // all   other fields defaulted      }       // 包含另一个"Map"的构造函数      public HashMap(Map<? extends K, ? extends V> m) {          this.loadFactor = DEFAULT_LOAD_FACTOR;          putMapEntries(m, false);//下面会分析到这个方法      }       // 指定"容量大小"的构造函数      public HashMap(int initialCapacity) {          this(initialCapacity, DEFAULT_LOAD_FACTOR);      }       // 指定"容量大小"和"加载因子"的构造函数      public HashMap(int initialCapacity, float loadFactor) {          if (initialCapacity < 0)              throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity);          if (initialCapacity > MAXIMUM_CAPACITY)              initialCapacity = MAXIMUM_CAPACITY;          if (loadFactor <= 0 || Float.isNaN(loadFactor))              throw new IllegalArgumentException("Illegal load factor: " + loadFactor);          this.loadFactor = loadFactor;          this.threshold = tableSizeFor(initialCapacity);      }Copy to clipboardErrorCopied
  putMapEntries()   方法源码分析: final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {     int s = m.size();     if (s > 0) {         // 判断table是否已经初始化         if (table == null) { // pre-size             // 未初始化,s为m的实际元素个数             float ft = ((float)s / loadFactor) + 1.0F;             int t = ((ft < (float)MAXIMUM_CAPACITY) ?                     (int)ft : MAXIMUM_CAPACITY);             // 计算得到的t大于阈值,则初始化阈值             if (t > threshold)                 threshold = tableSizeFor(t);         }         // 已初始化,并且m元素个数大于阈值,进行扩容处理         else if (s > threshold)             resize();         // 将m中的所有元素添加至HashMap中         for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {             K key = e.getKey();             V value = e.getValue();             putVal(hash(key), key, value, false, evict);         }     } }Copy to clipboardErrorCopied3.2 put() 方法源码分析
  HashMap   仅提供了  put()   用于添加元素, putVal()   只是给  put()   内部调用的一个方法。
  putVal()   添加元素的分析: 如果定位到的数组位置没有元素 就直接插入。 如果定位到的数组位置有元素,则比较要插入的 key ,如果 key 相同则直接覆盖,如果 key 不同,就判断当前位置是否是一个树节点,如果是就调用  e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value)   将元素添加进去。如果不是就遍历链表插入。
  注意:直接覆盖之后不会有后续的操作了。 当链表长度大于阈值(默认为 8)并且  HashMap   数组长度超过 64 的时候才会执行链表转红黑树的操作,否则就只是对数组扩容 。public V put(K key, V value) {     return putVal(hash(key), key, value, false, true); }  final V putVal(int hash, K key, V value, boolean onlyIfAbsent,                    boolean evict) {     Node[] tab; Node p; int n, i;     // table未初始化或者长度为0,进行扩容     if ((tab = table) == null || (n = tab.length) == 0)         n = (tab = resize()).length;     // (n - 1) & hash 确定元素存放在哪个桶中,桶为空,新生成结点放入桶中(此时,这个结点是放在数组中)     if ((p = tab[i = (n - 1) & hash]) == null)         tab[i] = newNode(hash, key, value, null);     // 桶中已经存在元素(处理hash冲突)     else {         Node e; K k;         // 判断table[i]中的元素是否与插入的key一样,若相同那就直接使用插入的值p替换掉旧的值e。         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);                     // 结点数量达到阈值(默认为 8 ),执行 treeifyBin 方法                     // 这个方法会根据 HashMap 数组来决定是否转换为红黑树。                     // 只有当数组长度大于或者等于 64 的情况下,才会执行转换红黑树操作,以减少搜索时间。否则,就是只是对数组扩容。                     if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st                         treeifyBin(tab, hash);                     // 跳出循环                     break;                 }                 // 判断链表中结点的key值与插入的元素的key值是否相等                 if (e.hash == hash &&                     ((k = e.key) == key || (key != null && key.equals(k))))                     // 相等,跳出循环                     break;                 // 用于遍历桶中的链表,与前面的e = p.next组合,可以遍历链表                 p = e;             }         }         // 表示在桶中找到key值、hash值与插入元素相等的结点         if (e != null) {             // 记录e的value             V oldValue = e.value;             // onlyIfAbsent为false或者旧值为null             if (!onlyIfAbsent || oldValue == null)                 //用新值替换旧值                 e.value = value;             // 访问后回调             afterNodeAccess(e);             // 返回旧值             return oldValue;         }     }     // 结构性修改     ++modCount;     // 实际大小大于阈值则扩容     if (++size > threshold)         resize();     // 插入后回调     afterNodeInsertion(evict);     return null; }Copy to clipboardErrorCopied3.3 get() 方法源码分析public V get(Object key) {     Node e;     return (e = getNode(hash(key), key)) == null ? null : e.value; }  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) {             // 在树中get             if (first instanceof TreeNode)                 return ((TreeNode)first).getTreeNode(hash, key);             // 在链表中get             do {                 if (e.hash == hash &&                     ((k = e.key) == key || (key != null && key.equals(k))))                     return e;             } while ((e = e.next) != null);         }     }     return null; }Copy to clipboardErrorCopied3.4 resize() 方法源码分析
  注意在程序编写中应尽可能的避免使用resize(),因为每次的扩容都会遍历hash表所有元素对hash重新分配,非常的耗时。 final Node[] resize() {     Node[] oldTab = table;     int oldCap = (oldTab == null) ? 0 : oldTab.length;     int oldThr = threshold;     int newCap, newThr = 0;     if (oldCap > 0) {         // 超过最大值就不再扩充了,就只好随你碰撞去吧         if (oldCap >= MAXIMUM_CAPACITY) {             threshold = Integer.MAX_VALUE;             return oldTab;         }         // 没超过最大值,就扩充为原来的2倍         else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY)             newThr = oldThr << 1; // double threshold     }     else if (oldThr > 0) // initial capacity was placed in threshold         newCap = oldThr;     else {         // signifies using defaults         newCap = DEFAULT_INITIAL_CAPACITY;         newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);     }     // 计算新的resize上限     if (newThr == 0) {         float ft = (float)newCap * loadFactor;         newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? (int)ft : Integer.MAX_VALUE);     }     threshold = newThr;     @SuppressWarnings({"rawtypes","unchecked"})         Node[] newTab = (Node[])new Node[newCap];     table = newTab;     if (oldTab != null) {         // 把每个bucket都移动到新的buckets中         for (int j = 0; j < oldCap; ++j) {             Node e;             if ((e = oldTab[j]) != null) {                 oldTab[j] = null;                 if (e.next == null)                     newTab[e.hash & (newCap - 1)] = e;                 else if (e instanceof TreeNode)                     ((TreeNode)e).split(this, newTab, j, oldCap);                 else {                     Node loHead = null, loTail = null;                     Node hiHead = null, hiTail = null;                     Node next;                     do {                         next = e.next;                         // 原索引                         if ((e.hash & oldCap) == 0) {                             if (loTail == null)                                 loHead = e;                             else                                 loTail.next = e;                             loTail = e;                         }                         // 原索引+oldCap                         else {                             if (hiTail == null)                                 hiHead = e;                             else                                 hiTail.next = e;                             hiTail = e;                         }                     } while ((e = next) != null);                     // 原索引放到bucket里                     if (loTail != null) {                         loTail.next = null;                         newTab[j] = loHead;                     }                     // 原索引+oldCap放到bucket里                     if (hiTail != null) {                         hiTail.next = null;                         newTab[j + oldCap] = hiHead;                     }                 }             }         }     }     return newTab; }Copy to clipboardErrorCopied
  至此HashMap源码分析到此为止了,在这里提出一个疑问,既然HashMap是线程不安全的,那么实际项目中我们又怎么解决这个问题呢?关注+收藏,下期我们来解决一下这个问题。

手脚冷得像冰块?喝1汤灸1穴搓1处,让手脚暖起来冬天就是个大流氓,每次一来就冻手冻脚的。不仅是出门在外,就连坐在办公室躺在床上,手脚都是冷冰冰的!特别是那些本来就容易手脚冰冷的朋友,是坐也坐不安稳睡也睡不香。该怎么办?先说说,为玩游戏手机卡顿?电脑配置不够用?一招轻松解决最近一段时间手游阴阳师明日之后哈利波特魔法觉醒等多款手游桌面版停止使用的消息,让很多玩家又回到了真正的手游时代。一些习惯了PC操作的玩家们突然变得难以适应,也有不少玩家选择转战模拟12月预算4000元怎么选手机这四款良心推荐款款都性能强任你随便选今年的新机发布也告一段落了,今年的新机老机那么多,很多朋友私信我说都12月了4000元怎么去选择一款合适的手机,4000元这个价位手机性能还是很强悍的,很多机型都是当家的旗舰机,非鸿蒙系统使用体验,MIUI真的一言难尽小编以前的主力机是荣耀magic2,后面换机打算换华为mate40pro的,但是刚出来很无奈,抢不到。恰好当时magic2烧屏了,换机需求比较大,当时就有两个选择,小米10至尊纪念给肠道减负,清肠好物盘点蔬菜中的膳食纤维对肠道有一定的疏通作用,那么,哪些蔬菜刚好呢?花菜花菜每100g含量热量(大卡)20。00,碳水化合物(克)4。20,脂肪(克)0。20,蛋白质(克)1。70,纤维网曝快本改名主持团体解散独留何炅,布瑞吉加盟意想不到快乐大本营作为一部棚内综艺,开播20多年一直屹立不倒,是90后的一代记忆,在娱乐圈这个大染缸快乐大本营也不免惹俗何炅在节目中称希望粉丝以后送礼的时候,标签不要贴太紧,要不然不好送给豪门女婿多难当?李政宰和三星前太子妃热恋7年,48岁还没能结婚鱿鱼游戏大火之后,韩国影帝李政宰的资源也是越来越好。借着这股东风,李政宰更是成了GUCCI的全球品牌大使。看看这潇洒的状态,哪里像是个已经快五十岁的大叔呢?不过,今年48岁的李政宰布丽吉特拥有巴黎最美的腿,和小11岁的雅子同框,小腿细一圈1963年,法国巴黎女人最美腿比赛上,经过一番紧张的评选后,最终是七号美腿占据上风,俘获了大众的心。而布丽吉特的腿,比起当年的那双七号美腿来说,也是有过之无不及,有人评价说她跟各国11月SUV头部争夺波谲云诡特斯拉ModelY喜忧参半来源中国经济网郭跃11月,国内SUV市场依旧低迷徘徊,共售出84。1万辆,同比下滑12。6,迎来七连跌。相较于略显寡淡的整体市场表现,头部阵营的争夺则是波谲云诡,延续了近几个月来竞冬天,试着假假露脚踝吧,显瘦显高效果超好,还不挨冻不知道大家发现没有,这两年时装咖们也越来越懂得养生了。以前到了冬天,她们依然很喜欢露腰露腿,美则美矣,但如果我们跟着学,分分钟冻成狗。现在慵懒风刮得猛,时装精们发现露得太明显容易显冬季流行咖色长大衣,学时髦精们这样穿,不仅衬肤色还显气质提到冬季穿搭,最不能不提的就是各种长款大衣了,人到中年后多选择长款大衣,也能带来众多优势。比如长款大衣修身显瘦,可以让你看起来更苗条窈窕,加上长款大衣大多比较实用,更是备受青睐。不
刘德华被庞龙嘲不专业,还在东北惨遭掌掴,赵本山仗义解围东北遇贵人刘德华在东北惨遭掌掴,求助大哥向华强无果后,他究竟找了谁才保住性命?彼时刘德华正当红,打算来东北开演唱会。此事被嘉阳集团老总刘涌知道后,那是分外眼馋啊。向华强毕竟如若能让谁给的勇气?林俊杰邀请12人,mirror却只来一人,被骂后发声道歉林俊杰JJ20世界巡回演唱会在香港站开幕,JJ用精彩的演出感动了现场的歌迷。林俊杰一身白马王子造型,演奏着幻彩钢琴唱出记得揭开序幕,之后连唱木乃伊美人鱼茉莉雨和醉赤璧等多首热门歌曲四大在中国的好日子到头了?四大是全球知名的四个会计师事务所简称,他们分别是普华永道(PwC)德勤(DTT)毕马威(KPMG)和安永(EY)。而近日四大会计师事务所之一的德勤因作为中国华融审计机构存在严重审计缅甸北部,是人间天堂,还是人间地狱在缅甸北部,一个三不管的地方,那些别有用心的人把这里打造成你眼中的人间天堂。让那些没有多少社会经验和人生阅历的人趋之若鹜,最终成为他们谋取利益的工具。真实的缅北充斥的是暴力色情毒品快来赏花!石家庄裕西公园美人梅进入盛花期在石家庄裕西公园东主路,远远的就看见了一片绯红色的云霞,微风吹过,一树树美人梅婆娑摇曳,吸引了众多民前来拍照。美人梅,光听名字就觉得很美。梅是中国精神文明的象征,美人梅的花和梅花几美人梅处处绽放在静安寻觅花中混血每年三月四月,静安各处的美人梅绚丽绽放,以繁花作礼,为市民游客送上春天的祝福。美人梅是一种人工培育的观赏花,由重瓣宫粉型梅与红叶李杂交而成,属于园艺杂交种。美人梅既继承了红叶李的红美人鱼哈莉贝利曝写真影迷的反映让我震惊(17)1905电影网讯日前,迪士尼真人版电影小美人鱼的主演哈莉贝利登上Edition封面,演绎了一组符合她风格的时尚大片。写真中,哈莉梳着脏辫造型,身穿深V露肩华服,肩带上点缀着三亚旅拍婚纱照攻略,头发少的妹子怎么拍婚纱照好看?三亚旅拍婚纱照,头发少能不能戴假发?不少深受脱发困扰的妹子都有这种疑惑。看到别人拍的三亚婚纱照,女孩子长长的秀发,卷起来,编起来,或者是扎个丸子,再搭配上一些配饰,是非常好看的。但维多利亚的秘密时装秀的几件事,吉赛尔邦辰穿着世界最贵文胸每年的12月8日被称为天使之夜,著名的维多利亚的秘密时装秀将在这一天举行,许多人对此都十分感兴趣,下面我们就来分享几件你可能不知道的关于维多利亚的秘密时装秀的事情。第一届维多利亚的养生学习笔记为什么男生不护肤却比女生皮肤还要好?我们会发现一个奇怪的问题,很多男性平时根本不护理,甚至连最普通的护肤霜都不用,但是他们的皮肤却可能比女生还要好,不会有导致皱纹出现的元凶干燥。而有些女孩子平时一直不间断用保湿产品,vivo新机通过3C认证支持44W快充,预计为XFlip折叠屏手机IT之家3月21日消息,vivo现有一款型号为V2256A的新机通过了国家3C质量认证,支持44W有线充电,有数码博主称其为vivoXFlip。该机目前已经出现在了Geekbenc