Java8 HashMap源码分析


声明:本文转载自https://my.oschina.net/eumji025/blog/1610226,转载目的在于传递更多信息,仅供学习交流之用。如有侵权行为,请联系我,我会及时删除。

Hashmap介绍

Hashmap是基于hash表的map的实现,使用key-value形式存储键值对,并允许使用 null 值和 null 键,但是key只能有一个为null. Map不保证映射的顺序,其内部是根据hash值去模运算去排列的。hashmap内部使用entry数组作为存储的介质.

本文是基于Java8版本写的,因为Java8对hashMap有较大的改变,采用数组+链表+红黑树方式来记录键值对. 而且Java8还重写了resize方法,Java8之前很有可能造成扩容时,链表的循环问题.

源码解读

本文中的代码比较多,大多数的说明都在注释上体现了,所以可能文字上表述的不是很多,本文也尽可能介绍了一些java8中的新方法.需要注意的是其中很多地方都进行了修改和补充,看完本篇文章一定和之前看的Java7 Hashmap有非常多的不同之处,也可以思考一下为什么Java8要做这么多改变.

Node介绍

Node是map的接口中的内部接口Map.Entry的实现类,用于存储Hashmap中键值对的对象,是HashMap中非常重要的一个内部类,随便提一下,HashMap中有非常多的内部类,本文没做过多的介绍,读者可以自己翻看一下源码,因为篇幅实在太长了...在这里就简单的讲一下,大部分的内部类都是用于集合操作的,如keySet,values,entrySet等方法.

内部组成

static class Node<K,V> implements Map.Entry<K,V> { //key是不可变的 final K key; //value V value; //指向下一个entry对象,具体作用后面介绍 Node<K,V> next; //hash值 int hash; } 

Hashmap组成

Hashmap主要参数

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; //默认初始容量 static final int MAXIMUM_CAPACITY = 1 << 30; //最大容量 static final float DEFAULT_LOAD_FACTOR = 0.75f; //默认负载因子,相当于只有多少可用 transient Node<K,V>[] table;//要存储值的hash表 transient int size; //实际大小 int threshold; //阈值 final float loadFactor;//负载因子 transient int modCount;  //记录操作次数的 

相比之前版本的HashMap,Java8更钟爱与位操作. >> 表示逻辑右移 >>> 表示无符号逻辑右移.高位补零

Hashmap构造方法

//为空时候使用默认分配的大小16,负载因子0.75f,默认的容量为12,当size>threshold默认容量时候就会去扩容 public HashMap() {       this.loadFactor = 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); } 

HashMap方法

本文主要挑选几个比较常用且重要的方法进行分析.因为这写方法已经涵盖了大部分的方法调用关系.

hash方法 使用hashcode 异或 hashcode右移16位 得到hash值,等同于高位都取零 随带提一句,int类型的hashcode就是本身.

static final int hash(Object key) {     int h;     return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); } 

put方法解析 put方法,是HashMap中非常重要的方法,其中牵涉到了非常多的知识点,需要仔细的阅读.

 public V put(K key, V value) {     return putVal(hash(key), key, value, false, true); } /**  * onlyIfAbsent 是否替换,为true时,如果存在值则替换  * evict 主要用于LinkedHashMap移除最近未使用的节点  */ 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;     //如果不存在,直接put     if ((p = tab[i = (n - 1) & hash]) == null)         tab[i] = newNode(hash, key, value, null);     else {         Node<K,V> e; K k;         //根据条件获取不同的node节点,有可能tab[i]就是存在的元素         if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))             e = p;         //否则判断是树,添加         else if (p instanceof TreeNode)             e = ((TreeNode<K,V>)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;             //用于linkedhashmap,本文不做介绍             afterNodeAccess(e);             return oldValue;         }     }     //添加成功,判断是否要扩容     ++modCount;     if (++size > threshold)         resize();     //用于linkedhashmap     afterNodeInsertion(evict);     return null; } 

下面详细介绍一下扩容的方法.看起来非常庞大.其实逻辑不复杂

/**  * 有几种情况  * 1.当为空的时候,也就是没有初始化的时候  * 2.当到达最大值时候  * 3.普通扩容时候  */ final Node<K,V>[] resize() {     Node<K,V>[] oldTab = table;     int oldCap = (oldTab == null) ? 0 : oldTab.length;     int oldThr = threshold;     int newCap, newThr = 0;     //存在旧值     if (oldCap > 0) {         //大于2<<30 最大容量设置为2<<32 - 1         if (oldCap >= MAXIMUM_CAPACITY) {             //但是不移动.,没有空间移动             threshold = Integer.MAX_VALUE;             return oldTab;         }         //如果属于正常范围的扩容 容量 x2         else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&                 oldCap >= DEFAULT_INITIAL_CAPACITY)             newThr = oldThr << 1; // double threshold     }     //用户自己设定了初始化大小     else if (oldThr > 0) {         newCap = oldThr;     }     //如果没使用,使用默认值初始化     else {         newCap = DEFAULT_INITIAL_CAPACITY;         newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);     }     //用户自定义了map的初始化操作     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<K,V>[] newTab = (Node<K,V>[])new Node[newCap];     table = newTab;     //如果是扩容操作     if (oldTab != null) {         //遍历map的数据         for (int j = 0; j < oldCap; ++j) {             Node<K,V> 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<K,V>)e).split(this, newTab, j, oldCap);                 //如果没超过8个 是链表                 else { // preserve order                     Node<K,V> loHead = null, loTail = null;                     Node<K,V> hiHead = null, hiTail = null;                     Node<K,V> next;                     //此处的操作是这样子的 因为是扩容一倍的操作,所以与旧的容量进行与操作后只有两个值0 和 1                     //如果是0就位置不变,如果是1就移动n+old的位置,                     //个人认为这么做的好处是:                     /**                      * 1.不会像之前1.7发生循环依赖的问题                      * 2.从概率的角度上来看可以均匀分配,(一般来说高位和低位比例差不多)                      * 3.提高效率                      */                     do {                         next = e.next;                         //如果和旧容量位运算后的值是0,记得当前节点和存放在链表的尾部                         if ((e.hash & oldCap) == 0) {                             if (loTail == null)                                 loHead = e;                             else                                 loTail.next = e;                             loTail = e;                         }                         //同上                         else {                             if (hiTail == null)                                 hiHead = e;                             else                                 hiTail.next = e;                             hiTail = e;                         }                     } while ((e = next) != null);                     //为0的还是存放在当前位置                     if (loTail != null) {                         loTail.next = null;                         newTab[j] = loHead;                     }                     //为1的就放在扩容的j + oldCap那边去                     if (hiTail != null) {                         hiTail.next = null;                         newTab[j + oldCap] = hiHead;                     }                 }             }         }     }     return newTab; } 

因为不像Java8之前的HashMap有初始化操作,此处选择将初始化和扩容放在了一起,并且又增加了红黑树的概念,所以导致整个方法的判断次数非常多,也是这个方法比较庞大的主要原因.

值得一体的是,在扩容后重新计算位置的时候,对链表进行优化,有兴趣可以搜索一下hashmap导致cpu百分之百的问题 而在Java中通过巧妙的进行&操作,然后获得高位是为0还是1.最终移动的位置就是低位的链表留在原地,高位的放在index+oldsize的地方就可以了,不用为每一个元素计算hash值,然后移动到对应的位置,再判断是否是链表,是否需要转换成树的操作.如下所示.

hashcode: 1111111111111101212 oldcap:   0000000000000010000 

很容易知道这个&操作之后就是为0,因为oldcap都是2的倍数,只有高位为1,所以通过&确认高位要比%取余高效. 此时在看一下上面的扩容操作也许就更清晰了.

下面介绍一些newNode方法.就是新建一个节点.可以思考一下为什么要把newNode抽离出来?文章末尾给答案.

Node<K,V> newNode(int hash, K key, V value, Node<K,V> next) {     return new Node<>(hash, key, value, next); } 

添加节点到红黑树的方法是Java8中新添加的,需要满足链表的长度到8,才会转换成红黑树,其主要目的是防止某个下标处的链表太长,导致在找到的时候速度很慢,下面看一下实现

//尝试着往树节点添加值 final TreeNode<K,V> putTreeVal(HashMap<K,V> map, Node<K,V>[] tab,int h, K k, V v) {     Class<?> kc = null;     boolean searched = false;     //找到根节点     TreeNode<K,V> root = (parent != null) ? root() : this;     for (TreeNode<K,V> p = root;;) {         int dir, ph; K pk;         if ((ph = p.hash) > h)             dir = -1;         else if (ph < h)             dir = 1;         //存在的话直接返回,用于替换         else if ((pk = p.key) == k || (k != null && k.equals(pk)))             return p;         //判断节点类型是否相同,不相同         else if ((kc == null &&                   (kc = comparableClassFor(k)) == null) ||                  (dir = compareComparables(kc, k, pk)) == 0) {             //没有搜索过,搜索子节点,搜过了说明没有就跳过.             if (!searched) {                 TreeNode<K,V> q, ch;                 searched = true;                 //去子节点去找                 if (((ch = p.left) != null &&(q = ch.find(h, k, kc)) != null) ||((ch = p.right) != null &&                      (q = ch.find(h, k, kc)) != null))                     return q;             }             //对比hash值,决定查找的方向             dir = tieBreakOrder(k, pk);         }          TreeNode<K,V> xp = p;         //找到子节点为空,就可以加进去,设置层级关系         if ((p = (dir <= 0) ? p.left : p.right) == null) {             Node<K,V> xpn = xp.next;             TreeNode<K,V> x = map.newTreeNode(h, k, v, xpn);             if (dir <= 0)                 xp.left = x;             else                 xp.right = x;             xp.next = x;             x.parent = x.prev = xp;             if (xpn != null)                 ((TreeNode<K,V>)xpn).prev = x;             moveRootToFront(tab, balanceInsertion(root, x));             return null;         }     } } 

这里简单的梳理一下流程. 1.从根节点查找,找到了返回,如果没找到,找字节点 2.判断往哪个方向去查找 3.如果不存在,在子节点末端添加新节点

下面再看一下树的split方法,主要是扩容操作,重新结算位置需要分裂树,之前讲过,扩容会根据和旧map容量进行&操作,移动高位为1的节点.并且验证新的节点列表是否需要重新转换成链表的形式.

final void split(HashMap<K,V> map, Node<K,V>[] tab, int index, int bit) {     TreeNode<K,V> b = this;     // 设置记录高低位的node,和链表一样都是计算高位是0还是1     TreeNode<K,V> loHead = null, loTail = null;     TreeNode<K,V> hiHead = null, hiTail = null;     int lc = 0, hc = 0;     for (TreeNode<K,V> e = b, next; e != null; e = next) {         next = (TreeNode<K,V>)e.next;         e.next = null;         //还是和旧的容量做位运算,为0的放在lo中         if ((e.hash & bit) == 0) {             //判断是否为头部             if ((e.prev = loTail) == null)                 loHead = e;             else                 loTail.next = e;             loTail = e;             ++lc;         }         //获取为1的放在hi中         else {             if ((e.prev = hiTail) == null)                 hiHead = e;             else                 hiTail.next = e;             hiTail = e;             ++hc;         }     }     //lo链表的处理     if (loHead != null) {         //如果小于7,那么当做链表处理         if (lc <= UNTREEIFY_THRESHOLD)             tab[index] = loHead.untreeify(map);         else {             //转换成树             tab[index] = loHead;             if (hiHead != null) // (else is already treeified)                 loHead.treeify(tab);         }     }     //同上     if (hiHead != null) {         if (hc <= UNTREEIFY_THRESHOLD)             tab[index + bit] = hiHead.untreeify(map);         else {             tab[index + bit] = hiHead;             if (loHead != null)                 hiHead.treeify(tab);         }     } } //把树转换成链表 final Node<K,V> untreeify(HashMap<K,V> map) {     Node<K,V> hd = null, tl = null;     for (Node<K,V> q = this; q != null; q = q.next) {       Node<K,V> p = map.replacementNode(q, null);       if (tl == null)         hd = p;       else         tl.next = p;       tl = p;     }     return hd; } 

转化成红黑树的详情本文就没详细介绍了,我相信很容易看懂. 这里借`美团点评技术团队`的一张图来展示一下put方法的流程

get方法

逻辑其实很简单,就是首先通过hashcode确认位置,然后分数据结构类型继续不同方式的查找

public V get(Object key) {     Node<K,V> e;     return (e = getNode(hash(key), key)) == null ? null : e.value; } 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; } 

remove方法

public V remove(Object key) {     Node<K,V> e;     return (e = removeNode(hash(key), key, null, false, true)) == null ?         null : e.value; } /**   * 1.寻找是否存在,如果存在分数据类型分别处理   * 2.如果为树,稍微复杂一点,需要判断是否要转换成链表,然后就是树的移动   */ final Node<K,V> removeNode(int hash, Object key, Object value,boolean matchValue, boolean movable) {   Node<K,V>[] tab; Node<K,V> p; int n, index;   //找到节点   if ((tab = table) != null && (n = tab.length) > 0 &&(p = tab[index = (n - 1) & hash]) != null) {       Node<K,V> node = null, e; K k; V v;       if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))           node = p;       else if ((e = p.next) != null) {          //从树节点获取           if (p instanceof TreeNode)               node = ((TreeNode<K,V>)p).getTreeNode(hash, key);           else {               do {                   if (e.hash == hash &&                       ((k = e.key) == key ||                        (key != null && key.equals(k)))) {                       node = e;                       break;                   }                   p = e;               } while ((e = e.next) != null);           }       }       //找到了对应的节点       if (node != null && (!matchValue || (v = node.value) == value ||                            (value != null && value.equals(v)))) {           //树节点处理 主要是两点,存在删除,删除了是否需要转换成链表           if (node instanceof TreeNode)               ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);           //一个元素           else if (node == p)               tab[index] = node.next;           else               //指向node的下一个               p.next = node.next;           ++modCount;           --size;           afterNodeRemoval(node);           return node;       }   }   return null; } 

本文就不看树的详细删除过程了,内容太多太长.可以自己想象一下树的删除过程. replace方法

@Override public V replace(K key, V value) {     Node<K,V> e;     //找到节点替换     if ((e = getNode(hash(key), key)) != null) {         V oldValue = e.value;         e.value = value;         //与linkedhashmap有关         afterNodeAccess(e);         return oldValue;     }     return null; }  

从基本的方法中我们可以看出,最复杂的就是put方法,put方法设计非常多的方法,后续的get,replace,remove都是建立在put方法基础之上.

补充方法

上面介绍了几个基本的方法,另外现在介绍一些有用的小方法.都是在Java8新增的.

merge方法 merge方法的主要作用是如果不存在就进行添加,如果存在的话按照自己指定的remappingFunction进行操作,如果操作之后value为null的话删除元素,否则替换,下面是代码详情

public V merge(K key, V value,BiFunction<? super V, ? super V, ? extends V> remappingFunction) {     if (value == null)         throw new NullPointerException();     if (remappingFunction == null)         throw new NullPointerException();     int hash = hash(key);     Node<K,V>[] tab; Node<K,V> first; int n, i;     int binCount = 0;     TreeNode<K,V> t = null;     Node<K,V> old = null;     //扩容操作     if (size > threshold || (tab = table) == null ||(n = tab.length) == 0)         n = (tab = resize()).length;     //通过key找到元素,分为树和链表两种情况     if ((first = tab[i = (n - 1) & hash]) != null) {         if (first instanceof TreeNode)             old = (t = (TreeNode<K,V>)first).getTreeNode(hash, key);         else {             Node<K,V> e = first; K k;             do {                 if (e.hash == hash &&                     ((k = e.key) == key || (key != null && key.equals(k)))) {                     old = e;                     break;                 }                 ++binCount;             } while ((e = e.next) != null);         }     }     //存在旧的结点,进行合并操作     if (old != null) {         V v;         if (old.value != null)             //具体的合并操作             v = remappingFunction.apply(old.value, value);         else             v = value;         //合并成功,执行afterNodeAccess方法,子类linkedHashMap有用         if (v != null) {             old.value = v;             afterNodeAccess(old);         }         //合并之后没有值,删除元素         else             removeNode(hash, key, null, false, true);         return v;     }     //没有旧结点,直接添加,考虑扩容,链表和树的情况     if (value != null) {         if (t != null)             t.putTreeVal(this, tab, hash, key, value);         else {             tab[i] = newNode(hash, key, value, first);             if (binCount >= TREEIFY_THRESHOLD - 1)                 treeifyBin(tab, hash);         }         ++modCount;         ++size;         afterNodeInsertion(true);     }     return value; } 

compute方法

public V compute(K key,BiFunction<? super K, ? super V, ? extends V> remappingFunction) {   //省略部分基本和merge方法的前半段一直,不重复展示    //获取旧元素   V oldValue = (old == null) ? null : old.value;   //计算出新值   V v = remappingFunction.apply(key, oldValue);   //存在旧元素,旧赋值到旧元素上   if (old != null) {       if (v != null) {           old.value = v;           afterNodeAccess(old);       }       //计算结果为空则删除       else           removeNode(hash, key, null, false, true);   }   //r如果没有旧元素,但计算出的值不为空,添加操作,和merge方法相同,省略    return v; }  

看到这里我相信大家都有疑问,为什么要放两个几乎相同的两个方法,

我们详细对比一下两个方法,发现会有几点不同:

  1. 参数不同,merge方法要求value不为null,compute方法没有要求;
  2. merge方法要求old.value也不为null.compute的方法依然没有要求.

另外compute还有两个扩展方法,简单介绍一下.

computeIfAbsent方法 如果oldvalue不为null就不替换,如果计算后的值不存在直接返回,否则如果old不为null,通过key计算出值替换,否则添加到map中

 public V computeIfAbsent(K key,Function<? super K, ? extends V> mappingFunction) {     //扩容等操作省略,同上       V oldValue;       if (old != null && (oldValue = old.value) != null) {           afterNodeAccess(old);           return oldValue;       }     //注意这里     V v = mappingFunction.apply(key);     if (v == null) {         return null;     } else if (old != null) {         old.value = v;         afterNodeAccess(old);         return v;     }     //添加操作省略 } 

computeIfPresent方法 不进行扩容的判断(因为不存在找不到就添加这样的操作).直接通过key查找,如果找到,且计算的结果不为null,替换,否则就直接删除

public V computeIfPresent(K key,BiFunction<? super K, ? super V, ? extends V> remappingFunction) {         if (remappingFunction == null)             throw new NullPointerException();         Node<K,V> e; V oldValue;         int hash = hash(key);         if ((e = getNode(hash, key)) != null &&             (oldValue = e.value) != null) {             //注意这里             V v = remappingFunction.apply(key, oldValue);             if (v != null) {                 e.value = v;                 afterNodeAccess(e);                 return v;             }             else                 removeNode(hash, key, null, false, true);         }         return null;     } 

再次总结一下三个compute方法的异同点

  1. compute方法和computeIfPresent方法都需要oldValue,computeIfAbsent不需要
  2. compute的remappingFunction.apply方法不限制oldvalue是否为null,但是computeIfPresent限制必须不为null才进行,computeIfAbsent方法必须要old或者oldvalue为null才会进行后续操作
  3. computeIfPresent只有oldvalue存在则进行apply方法,然后根据条件替换或者删除操作,而compute和computeIfAbsent方法则是如果old不存在,还会根据条件额外进行添加操作

简单点说就是:

  1. 如果是不存在oldvalue才进行操作,这也可以认为我们在声明了String a = null这样的操作,现在需要进行初始化,选择computeIfAbsent方法,
  2. 如果必须存在oldvalue才操作,而且只进行删除或者修改的操作则选择computeIfPresent方法,类似看看还有没有修改的价值,没价值就干掉了.
  3. 其他选择compute方法

快速失败和安全失败问题

一:快速失败(fail—fast)     在用迭代器遍历一个集合对象时,如果遍历过程中对集合对象的内容进行了修改(增加、删除、修改),则会抛出Concurrent Modification Exception。     原理:迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个 modCount 变量。集合在被遍历期间如果内容发生变化,就会改变modCount的值。每当迭代器使用hashNext()/next()遍历下一个元素之前,都会检测modCount变量是否为expectedmodCount值,是的话就返回遍历;否则抛出异常,终止遍历。    注意:这里异常的抛出条件是检测到 modCount!=expectedmodCount 这个条件。如果集合发生变化时修改modCount值刚好又设置为了expectedmodCount值,则异常不会抛出。因此,不能依赖于这个异常是否抛出而进行并发操作的编程,这个异常只建议用于检测并发修改的bug。    场景:java.util包下的集合类都是快速失败的,不能在多线程下发生并发修改(迭代过程中被修改)。 二:安全失败(fail—safe)    采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。    原理:由于迭代时是对原集合的拷贝进行遍历,所以在遍历过程中对原集合所作的修改并不能被迭代器检测到,所以不会触发Concurrent Modification Exception。    缺点:基于拷贝内容的优点是避免了Concurrent Modification Exception,但同样地,迭代器并不能访问到修改后的内容,即:迭代器遍历的是开始遍历那一刻拿到的集合拷贝,在遍历期间原集合发生的修改迭代器是不知道的。    场景:java.util.concurrent包下的容器都是安全失败,可以在多线程下并发使用,并发修改。 

解答

解释一下刚才在文章设置的悬念,为什么要把newNode方法单独提出来,其实这里很大一部分原因是因为Linkedhashnap会需要重写此方法进行额外的操作.具体是什么可以自己查看一下源码,或者看我的另一篇文章,map集合类总结.

总结

改版后的HashMap看起来更加的庞大和神秘了,因为相比之前看起来可能方法更大而且还有红黑树这个数据结构,看到代码就会让人感觉好难受,同时也呼吁每个人都不要写这么庞大的方法.尽可能将方法拆小.变得更加的简洁明了.但是Java8对HashMap的改变,使得HashMap在一定程度上提升了性能,并且还新填了不少的方法.

当然本文只是个人的一些看法,如果存在不足或者错误的地方,欢迎大家指正!!!

结语

与君共勉!!!

本文发表于2018年01月21日 16:32
(c)注:本文转载自https://my.oschina.net/eumji025/blog/1610226,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责。如有侵权行为,请联系我们,我们会及时删除.

阅读 1720 讨论 0 喜欢 0

抢先体验

扫码体验
趣味小程序
文字表情生成器

闪念胶囊

你要过得好哇,这样我才能恨你啊,你要是过得不好,我都不知道该恨你还是拥抱你啊。

直抵黄龙府,与诸君痛饮尔。

那时陪伴我的人啊,你们如今在何方。

不出意外的话,我们再也不会见了,祝你前程似锦。

这世界真好,吃野东西也要留出这条命来看看

快捷链接
网站地图
提交友链
Copyright © 2016 - 2021 Cion.
All Rights Reserved.
京ICP备2021004668号-1