刷完HashMap源码,我们一起进大厂(hashmap1.7源码)

  本篇文章为你整理了刷完HashMap源码,我们一起进大厂(hashmap1.7源码)的详细内容,包含有hashmap1.8源码 hashmap1.7源码 hashmap源码分析和实现原理 hashmap的源码,实现原理,底层结构 刷完HashMap源码,我们一起进大厂,希望能帮助你了解 刷完HashMap源码,我们一起进大厂。

  概念:

  HashMap 是一个利用散列表(哈希表)原理来存储元素的集合,是根据Key value而直接进行访问的数据结构

  在 JDK1.7 中,HashMap 是由 数组+链表构成的。

  在 JDK1.8 中,HashMap 是由 数组+链表+红黑树构成

  回顾: 数组、链表(优势和劣势)

  数组: 优势:数组是连续的内存,查询快(o1) 劣势:插入删除O(N)
 

  链表: 优势:不是连续的内存,随便插入(前、中间、尾部) 插入O(1) 劣势:查询慢O(N)

  思考?

  

为什么是JDK1.8 是数组+链表+红黑树???

 

  

 

  HashMap变化历程

  1.7的数据结构:链表变长,效率低 了!!!

  1.8的数据结构:

  数组+链表+红黑树

  链表--树(链长度 8、数组长度大于64)

  备注:现在重点讲map,不讲树的操作。树在算法课里有详细学习

  总结:

  JDK1.8使用红黑树,其实就是为了提高查询效率

  因为,1.7的时候使用的数组+链表,如果链表太长,查询的时间复杂度直接上升到了O(N)

  2.2 HashMap继承体系

  目标:梳理map的继承关系

  总结

  HashMap已经继承了AbstractMap而AbstractMap类实现了Map接口

  那为什么HashMap还要在实现Map接口呢?

  据 java 集合框架的创始人Josh Bloch描述,这样的写法是一个失误。

  在java集合框架中,类似这样的写法很多,最开始写java集合框架的时候,他认为这样写,在某些地方可能是有价值的,直到他意识到错了。

  显然的,JDK的维护者,后来不认为这个小小的失误值得去修改,所以就这样存在下来

  
1)目标:

  通过阅读HashMap(since1.2)源码,我们可以知道以下几个问题在源码是如何解决的

  (1)HashMap的底层数据结构是什么?

  (2)HashMap中增删改查操作的底部实现原理是什么?

  (3)HashMap是如何实现扩容的?

  (4)HashMap是如何解决hash冲突的?

  (5)HashMap为什么是非线程安全的?

  2)测试代码如下

  

package com.mmap;

 

  import org.openjdk.jol.info.ClassLayout;

  import java.util.ArrayList;

  import java.util.ConcurrentModificationException;

  import java.util.HashMap;

  import java.util.List;

  import java.util.concurrent.ConcurrentHashMap;

  public class MMapTest {

   public static void main(String[] args) {

   HashMap Integer, String m = new HashMap Integer, String //尾插

   //断点跟踪put

   m.put(1, "001");

   m.put(1, "002");

   m.put(17, "003");//使用17可hash冲突(存储位置相同)

   System.out.println(ClassLayout.parseInstance(m).toPrintable());

   //断点跟踪get

   System.out.println(m.get(1));//返回002(数组查找)

   System.out.println(m.get(17));//返回003(链表查找)

   //断点跟踪remove

   m.remove(1);//移除

   System.out.println(m);

   m.remove(1, "002");//和上面的remove走的同一个代码

  

 

  3)关于hashMap基本结构的验证

  先来个小验证,几乎地球人都知道map是 数组 + 链表 结构,那我们先来验证一下

  再来看debug结果:

  验证了基本结构,那为啥1和17就在一块了?到底谁和谁放在一个链上呢?内部到底怎么运作的?往下看 ↓

  2.3.1 成员变量与内部类

  目标:先了解一下它的基本结构

  回顾:位运算(下面还会频繁用到)

  1 4

  二进制相当于1右边补4个0:10000

  十进制相当于1 x 2的4次方 , 也就是 16

  二进制运算是因为它的计算效率高

  

 

 

   static final int DEFAULT_INITIAL_CAPACITY = 1 4; //16,默认数组容量:左位移4位,即16

   static final int MAXIMUM_CAPACITY = 1 30;//最大容量:即2的30次幂

   static final float DEFAULT_LOAD_FACTOR = 0.75f;//负载因子:扩容使用,统计学计算出的最合理的

   static final int TREEIFY_THRESHOLD = 8;//链表转红黑树阈值

   static final int UNTREEIFY_THRESHOLD = 6;//当链表的值小 6, 红黑树转链表

   transient Node K,V [] table;//HashMap中的数组,中间状态数据

   transient Set Map.Entry K,V entrySet;//用来存放缓存,中间状态数据;

   transient int size;//size为HashMap中K-V的实时数量(重点),注意!不是table的长度!

   transient int modCount;//用来记录HashMap的修改次数,几个集合里都有它

   int threshold;//扩容临界点;(capacity * loadFactor)(重点)

   final float loadFactor;//负载因子 DEFAULT_LOAD_FACTOR = 0.75f赋值

  

 

  具体的key,value放在哪里呢?答案是一个静态内部类(1.8前是Entry)

  

 static class Node K,V implements Map.Entry K,V {//数组和链表上的节点,1.8前叫Entry

 

   final int hash;//扰动后的hash

   final K key;//map的key

   V value;//map的value

   Node K,V next;//下个节点

   Node(int hash, K key, V value, Node K,V next) { //构造函数,没啥说的

   this.hash = hash;

   this.key = key;

   this.value = value;

   this.next = next;//链表下一个

  

 

  2.3.2 HashMap构造器

  1)目标:学习下面的三个构造器,它们都干了哪些事情?

  

 public HashMap() {

 

   this.loadFactor = DEFAULT_LOAD_FACTOR; // 负载因子DEFAULT_LOAD_FACTOR = 0.75f

  

 

  

 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); // 注意,map里是没有capacity这个类变量的!

  

 

  map的构造函数就做了几个赋值这么点事?这么简单?错!接着往下看

  2)无参构造函数验证

  第一步:添加以下debug变量

  第二步:使用默认构造函数时,在put之前和之后分别debug以上变量信息对比看看

  put之前:

  之后

  毫无违和感,那么,我们接着往下!

  3)自定义初始化参数验证

  接下来我们胡搞一下,让容量=15,因子=0.5,猜一猜会发生什么?

  调试到put之后,再来看:

  源码剖析:

  在有参数构造时,最终tableSizeFor

  

 //capacity函数,初始化了table,就是table的length,否则取的是threshold

 

   final int capacity() {

   return (table != null) ? table.length :

   (threshold 0) ? threshold :

   DEFAULT_INITIAL_CAPACITY;

   //带参数的初始化,其实threshold调用的是以下函数:

   //这是什么神操作???

   //其实是将n转成2进制,右移再和自己取或,相当于把里面所有的0变成了1

   //最终目的:找到 =n的,1开头后面全是0的数。如果n=111 , 那就是 1000 ; 如果n=100,那就是它自己

   //而这个数,恰好就是2的指数,为后面的扩容做铺垫

   static final int tableSizeFor(int cap) {

   int n = cap - 1;

   n = n

   n = n

   n = n

   n = n

   n = n //到这一步n已经各个位都是1了。

   //范围校验,小于0返回1,大于最大值返回最大值,绝大多数正常情况下,返回n+1,也就是10000……

   return (n 0) ? 1 : (n = MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;

  
public static void main(String[] args) {

   System.out.println(tableSizeFor(9)); //9的二进制更能看出以下变化

   static final int tableSizeFor(int cap) {

   System.out.println(Integer.toBinaryString(cap)); //1001

   int n = cap - 1;

   System.out.println(Integer.toBinaryString(n)); //1000

   n = n //无符号右移,前面补0

   System.out.println(Integer.toBinaryString(n)); //右移再或,1100

   n = n

   System.out.println(Integer.toBinaryString(n)); //再移动2位, 1111

   n = n

   System.out.println(Integer.toBinaryString(n)); //就这么长,再迁移也是1111

   n = n

   System.out.println(Integer.toBinaryString(n));

   n = n

   System.out.println(Integer.toBinaryString(n)); //Integer的最大长度32位,16折半后迁移全覆盖

   System.out.println(Integer.toBinaryString(n+1));

   return n + 1; //+1后变为 10000 ,也就是16 , 2的4次方

  

 

  4)总结:

  map的构造函数没有你想象的那么简单!

  无参构造时,容量=16,因子=0.75。第一次插入数据时,才会初始化table、阈值等信息

  有参构造时,不会容忍你胡来,会取大于但是最接近你容量的2的指数倍(想一下为什么?提示:和扩容规则有关)

  无论哪种构造方式,扩容阈值最终都是 =(容量*因子)

  2.3.3 HashMap插入方法

  目标:图解+代码+断点分析put源码

  1)先了解下流程图

  2)关于key做hash值的计算

  当我们调用put方法添加元素时,实际是调用了其内部的putVal方法,第一个参数需要对key求hash值

  

 public V put(K key, V value) {

 

   return putVal(hash(key), key, value, false, true);//调用Map的putVal方法

  

 

  小提问:map里所谓的hash是直接用的key的hashCode方法吗?

  

 static final int hash(Object key) {

 

   int h;

   //【知识点】hash扰动

   return (key == null) ? 0 : (h = key.hashCode()) ^ (h 16);

  

 

  图解:

  结论:使用移位和异或做二次扰动,不是直接用的hashCode!

  3)核心逻辑

  

 final V putVal(int hash, K key, V value, boolean onlyIfAbsent,

 

   boolean evict) {//onlyIfAbsent:true不更改现有值;evict:false表示table为创建状态

   //几个临时变量:

   //tab=数组,p=插槽指针,n=tab的长度,i数组下标

   Node K,V [] tab; Node K,V int n, i;

   if ((tab = table) == null (n = tab.length) == 0)//数组是否null或者==0,第1次put为空

   //初始化数组(or扩容),所以table是在这里初始化的,不是new的时候!

   //初始时,n=16

   n = (tab = resize()).length; // resize,下面单独讲扩容

   //【知识点】为何1 与 17 在一个槽上!秘密就藏在寻址这里

   if ((p = tab[i = (n - 1) hash]) == null)//寻址:(n - 1) hash(重要!)

   tab[i] = newNode(hash, key, value, null);//当前插槽没有值,空的!将新node直接扔进去

   else { //有值,说明插槽上发生了碰撞,需要追加成链表了!

   //还是临时变量

   //e=是否找到与当前key相同的节点,找到说明是更新,null说明是新key插入

   //k=临时变量,查找过程中的key在这里暂存用

   Node K,V K k;

   if (p.hash == hash //如果正好,插槽第一个节点(p),跟插入的key相同

   ((k = p.key) == key (key != null key.equals(k))))

   e = p; //就将它赋值给e,注意!这时候还没覆盖上去,只是标记到e,发现了相同key的节点!

   else if (p instanceof TreeNode) //如果不是这个key,但是类型是一个红黑树节点

   //这说明当前插槽的链很长,已经变成红黑树了,就调putTreeVal,扔到这颗树上去

   //树的插入,这里不赘述,在数据结构课中有

   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);//然后把key封装成新node追加到尾巴上

   if (binCount = TREEIFY_THRESHOLD - 1) //链表长度计数如果 8转红黑树

   treeifyBin(tab, hash);//转成树

   break;

   if (e.hash == hash

   ((k = e.key) == key (key != null key.equals(k))))

   break;// 如果遍历过程中找到相同key,那就赋给e,break跳出for循环,执行后面的逻辑

   p = e;

   if (e != null) { // 如果e非空,说明前面一顿猛如虎的操作后,找到了相同的key

   V oldValue = e.value;

   if (!onlyIfAbsent oldValue == null)

   e.value = value;// 看看onlyIfAbsent,如果让覆盖那就覆盖,不让那就算了

   afterNodeAccess(e);

   return oldValue;// 返回覆盖前的value值,也就是put方法的返回值

   ++modCount;//用来记录HashMap的修改次数

   if (++size threshold)//key的数量是否大于阈值

   resize();//如果size大于threshold,就需要进行扩容

   afterNodeInsertion(evict);

   return null;

  

 

  4)重点(寻址计算):

  接上文,关于hash值取得后,放入tab的哪个插槽,也就是所谓的寻址我们重点来讲

  

(n - 1) hash

 

  

 

  我们还是以开始的例子,1和17为例,他们的hash计算后正好是1和17本身,我们可以验证一下

  

Integer i = new Integer(1);

 

  Integer j = new Integer(17);

  System.out.println(i.hashCode() ^ i.hashCode() 16); //1

  System.out.println(j.hashCode() ^ j.hashCode() 16); //17

  

 

  开始位运算

  

默认n=16,n-1也就是15,二进制是 1111

 

  那么 15 1

  1 1 1 1

  0 0 0 1

  与运算后 = 1

  再来看15 17,17是 10001

   1 1 1 1

  1 0 0 0 1

  与运算后 = 1

  所以,1和17肯定会落在table的1号插槽上!两者会成为链表,解释了我们前面的案例

  原理:不管你算出的hash是多少,超出tab长度的高位被抹掉,低位是多少就是你所在的槽的位置,也就是table的下标

  

 

  思考:为什么不用mod(模运算)进行寻址?mod也能保证不会超出数组边界,岂不是更简单直观?

  

package com.mmap;

 

  public class CMod {

   public static void main(String[] args) {

   bit();

   mod();

   public static void bit() {

   int a ;

   long start = System.currentTimeMillis();

   //同样的计算次数,先位运算,后取余

   for (int i = 1; i Integer.MAX_VALUE; i++) {

   a = 1

   long end = System.currentTimeMillis();

   System.out.println("BIT耗时 " + (end - start));

   public static void mod() {

   int a ;

   long start = System.currentTimeMillis();

   for (int i = 1; i Integer.MAX_VALUE; i++) {

   a = 1 % i;

   long end = System.currentTimeMillis();

   System.out.println("MOD耗时 " + (end - start));

  

 

  跑一下试试?

  结论:

  一切为了性能

  2.3.4 HashMap扩容方法

  目标:图解+代码(map扩容与数据迁移)

  注意:扩容复杂、绕、难

  备注:线程不安全的

  
迁移前:长度8 扩容临界点6(8*0.75)

  迁移过程

  核心源码resize方法

  

//注意!该方法兼容了初始化和扩容,所以比较难理清楚!

 

  //需要对照上面的图例来同步讲解。

  final Node K,V [] resize() {

   Node K,V [] oldTab = table; //旧的数组先拿出来

   int oldCap = (oldTab == null) ? 0 : oldTab.length;//旧数组是null,那就是初始化咯

   int oldThr = threshold;//扩容临界点(旧)

   int newCap, newThr = 0;//临时变量,数组容量(新)、扩容临界点(新)

   if (oldCap 0) {

   // 扩容的时候调用

   if (oldCap = MAXIMUM_CAPACITY) { //如果旧值达到上限

   threshold = Integer.MAX_VALUE; //扩容阈值也调到最大,从此再无意义

   return oldTab; //不扩了,直接返回旧的。上限了还扩什么扩

   //如果没到上限就计算新容量,注意这时候还没发生实际的数组扩容,真正的扩容迁数据操作在下面

   //将旧容量左移1位,也就是乘以2作为新容量,所以map是每次扩到之前的2倍

   //链表是右移1位再加上旧长度,也就是扩为原来的1.5倍,注意区别

   else if ((newCap = oldCap 1) MAXIMUM_CAPACITY

   oldCap = DEFAULT_INITIAL_CAPACITY)

   newThr = oldThr 1; // 同时,阈值也乘以2,为下次扩容做准备

   else if (oldThr 0)

   // HashMap(int initialCapacity, float loadFactor)初始化的时候调用

   // 将cap和thres相等,约定

   newCap = oldThr;

   else {

   // HashMap() 初始化的时候调用,注意前面验证过了,是在第一次put的时候调的

   newCap = DEFAULT_INITIAL_CAPACITY;

   newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);

   if (newThr == 0) {//如果新阈值为0,根据负载因子设置新阈值

   float ft = (float)newCap * loadFactor; // put之后的变化就在这里

   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) { //如果旧数组不为空,说明有数据要迁移

   for (int j = 0; j oldCap; ++j) { //遍历数组

   Node K,V //临时变量,记录当前指向的node

   if ((e = oldTab[j]) != null) {

   oldTab[j] = null;//gc处理

   if (e.next == null)

   newTab[e.hash (newCap - 1)] = e;//只一个节点,赋值到新数组的索引下即可

   else if (e instanceof TreeNode)// 如果变成了树,拆成俩拼到新table上去

   ((TreeNode K,V )e).split(this, newTab, j, oldCap);

   else { //如果是链表,拆成两个(重点!!!)

   Node K,V loHead = null, loTail = null;//低位链表(原位置i)

   Node K,V hiHead = null, hiTail = null;//高位链表(i+n位置)

   Node K,V next;

   do {

   next = e.next;

   if ((e.hash oldCap) == 0) { //oldCap=8,2进制为1000

   // 如果为0,说明e的hash没超出旧cap长度去,在低位不动即可

   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);

   //注意!在循环完成的时候,高低位链表还是俩独立的临时变量

   //下一步,将它放到新数组上去,才能算迁移完成

   if (loTail != null) {

   loTail.next = null;

   newTab[j] = loHead;//下标:原位置

   if (hiTail != null) {

   hiTail.next = null;

   newTab[j + oldCap] = hiHead;//下标:原位置+原数组长度 (重点!)

   //这地方诠释了为什么map要两倍扩容,对应位置位运算后,加上原长度就行了

   return newTab;//返回新数组

  

 

  总结(扩容与迁移):

  1、扩容就是将旧表的数据迁移到新表

  2、迁移过去的值需要重新计算下标,也就是他的存储位置

  3、关于位置可以这样理解:比如旧表的长度8、新表长度16

  旧表位置4有6个数据,假如前三个hashCode是一样的,后面的三个hashCode是一样的

  迁移的时候;就需要计算这6个值的存储位置

  4、如何计算位置?采用低位链表和高位链表;如果位置4下面的数据e.hash oldCap等于0,

  那么它对应的就是低位链表,也就是数据位置不变

  5、 e.hash oldCap不等于0呢?就要重写计算他的位置也就是j + oldCap,(4+8)= 12,就是高位链表位置(新数组12位置)

  2.3.5 HashMap获取方法

  目标:图解 (这个简单!)

  获取流程

  get主方法

  

 public V get(Object key) {

 

   Node K,V

   return (e = getNode(hash(key), key)) == null ? null : e.value;// 重点在getNode

  

 

  getNode方法

  

 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) {

   // 如果table对应的索引位置上有值

   if (first.hash == hash

   ((k = first.key) == key (key != null key.equals(k))))

   return first;// 看下第一个元素的key是不是要查找的那个,是的话,返回即可

   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;

  

 

  总结:

  查询思路比较简单,如果是数组直接返回、如果是红黑实例,就去树上去找,最后,去做链表循环查找

  2.3.6 HashMap移除方法

  目标:图解+断点分析remove源码

  移除流程

  tips:

  两个移除方法,参数上的区别

  走的同一个代码

  移除方法:一个参数

  

 public V remove(Object key) {

 

   Node K,V //// 定义一个节点变量,用来存储要被删除的节点(键值对

   return (e = removeNode(hash(key), key, null, false, true)) == null ?

   null : e.value;

  

 

  移除方法:二个参数

  

 @Override

 

   public boolean remove(Object key, Object value) {

   return removeNode(hash(key), key, value, true, true) != null;

  

 

  核心方法removeNode

  

 /**

 

   * Implements Map.remove and related methods

   * @param hash 扰动后的hash值

   * @param key 要删除的键值对的key,要删除的键值对的value,该值是否作为删除的条件取决于matchValue是否为true

   * @param value key对应的值

   * @param matchValue 为true,则当key对应的值和equals(value)为true时才删除;否则不关心value的值

   * @param movable 删除后是否移动节点,如果为false,则不移动

   * @return 返回被删除的节点对象,如果没有删除任何节点则返回null

   final Node K,V removeNode(int hash, Object key, Object value,

   boolean matchValue, boolean movable) {

   Node K,V [] tab; Node K,V int n, index;

   if ((tab = table) != null (n = tab.length) 0

   (p = tab[index = (n - 1) hash]) != null) { //注意,p是当前插槽上的头节点!

   //第一步:查,和上面的get操作一毛一样

   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;// 如果key相同,说明是头结点,将它赋给node

   else if ((e = p.next) != null) {

   //否则,沿着next一直查找

   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; //如果找到,赋值给node,并终止

   break;

   p = e; // 如果没找到,赋值给p,继续下一轮。

   } while ((e = e.next) != null);

   // 最终while结束的时候,p(前置)- node(要被删)

   //第二步:删

   //如果node不为空,说明根据key匹配到了要删除的节点

   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)// 不是树,那如果 node == p 的意思是该node节点就是首节点

   tab[index] = node.next;// 删掉头节点,第二个节点上位到数组槽上

   else // p是node的前置,说明上面查找的时候走的do while

   p.next = node.next;//如果不是,那就将p的后指针指向node的后指针,干掉node即可

   ++modCount;//HashMap的修改次数递增

   --size;// HashMap的元素个数递减

   afterNodeRemoval(node);

   return node;//返回删除后的节点

   return null;//找不到删除的node,返回null

  

 

  总结:

  
为什么说HashMap是线程不安全的

  我们从前面的源码分析也能看出,它的元素增删改的时候,没有任何加锁或者cas操作。

  而这里面各种++和--之类的操作,显然多线程下并不安全

  那谁安全呢?下节课我们分析

  本文由传智教育博学谷 - 狂野架构师教研团队发布
 

  如果本文对您有帮助,欢迎关注和点赞;如果您有任何建议也可留言评论或私信,您的支持是我坚持创作的动力
 

  转载请注明出处!

  以上就是刷完HashMap源码,我们一起进大厂(hashmap1.7源码)的详细内容,想要了解更多 刷完HashMap源码,我们一起进大厂的内容,请持续关注盛行IT软件开发工作室。

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: