一篇文章搞清楚java7/java8的HashMap、ConcurrentHashMap

本文篇幅较长建议阅读顺序
1、Java7 HashMap
2、Java8 HashMap
3、Java7 ConcurrentHashMap
4、Java8 ConcurrentHashMap
那既然需要切换jdk,那大家根据自己的情况选择阅读,互相之间我会尽量写的独立,不影响阅读,废话不多说,开始吧!

一、Java7 HashMap

java 7的HashMap相较于另外我要讲的三个应该是最简单的,它在日常开发中使用最多,但也是不能支持并发操作的,
我们首先看下他的类图
在这里插入图片描述
从这个类图可以看出HashMap继承了AbstractMap,里面的许多方法都是直接抛错的,需要子类重写,有兴趣的同学可以看一下,我这里就不再讲了,主要是给个图让大家知道他在哪个包里,继承自哪个类。
既然要讲HashMap,那我们先要讲HashMap的结构了
在这里插入图片描述
上面黄色虚线围起来的是一个数组,每一个绿色的实体都是一个Entry 实例,数组的每个index(也叫哈希桶/槽)上就是由一个个这样的Entry实例构成的单向链表,Entry包括四个属性 key(存入的key),value(key对应的value),next(下一个Entry实例),hash(hash值),好了看完了结构图,我们再来看一下HashMap的属性吧。

	// 默认容量大小 2的4次方 也就是 16
	static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
    // 可扩容的最大容量 2的30次方
    static final int MAXIMUM_CAPACITY = 1 << 30;
    //  负载因子(Load factor):HashMap在其容量自动增加前可达到多满的一种尺度
	// a. 负载因子越大、填满的元素越多 = 空间利用率高、但冲突的机会加大、查找效率变低(因为链表变长了)
	// b. 负载因子越小、填满的元素越少 = 空间利用率小、冲突的机会减小、查找效率高(链表不长)
    // 负载因子,默认 0.75f
    static final float DEFAULT_LOAD_FACTOR = 0.75f;
    // 空entry数组
    static final Entry<?,?>[] EMPTY_TABLE = {};
    // 存储数据Entry的数组
    transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
    // 键值对数量
    transient int size;
    // 扩容阈值(threshold):当哈希表的大小 ≥ 扩容阈值时,就会扩容哈希表(即扩充HashMap的容量) 
	// a. 扩容 = 对哈希表进行resize操作(即重建内部数据结构),从而哈希表将具有大约两倍的桶数
	// b. 扩容阈值 = 容量 x 加载因子,打个比方如果默认16容量,那么阈值就是12,当第13个键值对过来时则会触发扩容resize
    int threshold;
  	// 实际负载因子
    final float loadFactor;
    //Fail-Fast,前面讲arraylist已经讲过,快速失败机制,并发修改报错的判断依据
    transient int modCount;
    // 默认可容纳的扩容阈值
    static final int ALTERNATIVE_HASHING_THRESHOLD_DEFAULT = Integer.MAX_VALUE;

看完了属性,我们来看一下HashMap的构造器吧

// 实际上我们看所有的构造器都是调的这个方法,所以我们分析这个核心构造器
 public HashMap(int initialCapacity, float loadFactor) {
 		// 初始化容量 <0 抛错
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity);
        // 传入容量值大于最大容量默认为最大容量
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        // 如果负载因子小于等于0或者等于NaN,(NaN是Float的一种特殊的形式 is not a number),就会抛错;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " + loadFactor);
        this.loadFactor = loadFactor;
        threshold = initialCapacity;
        init();
 }
 // 带初始容量的构造函数
 public HashMap(int initialCapacity) {
 	// 指定 threshold,默认 loadFactor 为 0.75f
    this(initialCapacity, DEFAULT_LOAD_FACTOR);
 }
 // 不带参的构造函数
 public HashMap() {
 	// 空构造器默认的 threshold = 16 loadFactor = 0.75f
    this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
 }

看完了构造器,那我们老套路,从常用的API的一步步来分析他是怎么写的吧,首先我们看下 put(K key, V value),我们这边先默认下构造器使用的无参构造,就是 threshold = 16 loadFactor = 0.75f 这种情况下去分析

    public V put(K key, V value) {
    	// 如果是空数组则进行初始化 inflate(填充的意思),后面会展开这个方法,
        if (table == EMPTY_TABLE) {
        	// 传入 16
            inflateTable(threshold);
        }
        // 如果传入的可以为null,见下面分析
        if (key == null)
            return putForNullKey(value);
        // 计算hash
        int hash = hash(key);
        // 求得 index 位置
        int i = indexFor(hash, table.length);
        // 遍历table[i],看是否是替换数据,如果是替换则直接替换返回oldValue
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
        modCount++;
        // 新增entry 加入hashmap
        addEntry(hash, key, value, i);
        return null;
    }   

这个是put操作的全部源码,我们分开分析,先分析 inflateTable(threshold);

// 填充数组
   private void inflateTable(int toSize) {
   		// 见下面分析
        int capacity = roundUpToPowerOf2(toSize);
        // 16*0.75 = 12
        threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
        table = new Entry[capacity];
        // 初始化 HashSeed
        initHashSeedAsNeeded(capacity);
    }
    // 计算capacity
    private static int roundUpToPowerOf2(int number) {
        // 如果传入的threshold大于等于MAXIMUM_CAPACITY则默认MAXIMUM_CAPACITY,
        // Integer.highestOneBit(i) 取 i 这个数的二进制形式最左边的最高一位且高位后面全部补零,最后返回int型的结果
        // 反之则看threshold>1吗,大于1则取Integer.highestOneBit(30),30的二进制位10100高位取1其余补0那就是10000就是16
        return number >= MAXIMUM_CAPACITY
                ? MAXIMUM_CAPACITY
                : (number > 1) ? Integer.highestOneBit((number - 1) << 1) : 1;
    }

然后我们看下key 为 null 这种情况是怎么处理的

 // 放入key为null的value
    private V putForNullKey(V value) {
    	// key为空的默认放在table[0]上,遍历table[0],如果存在key为null的则替换
        for (Entry<K,V> e = table[0]; e != null; e = e.next) {
            if (e.key == null) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
        modCount++;
        // 放入数据,这里先不讲,在put操作后面可以统一讲一下
        addEntry(0, null, value, 0);
        return null;
    }

现在到了最关键的非空key是怎么放入hashmap的步骤了,我们还是把前面的代码再贴一下

public V put(K key, V value) {
    	// 如果是空数组则进行初始化 inflate(填充的意思),后面会展开这个方法,
        if (table == EMPTY_TABLE) {
        	// 传入 16
            inflateTable(threshold);
        }
        // 如果传入的可以为null
        if (key == null)
            return putForNullKey(value);
        // 计算hash
        int hash = hash(key);
        // 求得 index 位置
        int i = indexFor(hash, table.length);
        // 遍历table[i],看是否是替换数据,如果是替换则直接替换返回oldValue
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
        modCount++;
        // 新增entry 加入hashmap
        addEntry(hash, key, value, i);
        return null;
    }   
 // 首先我们看下 hash(key)这个方法,这个hash方法写的比较复杂
 // 里面object的hashcode的native方法,和String的key使用的sun的stringHash32,我就不深入展开了(主要是展开了我也讲不清QAQ)
 // 这里我们要注意:如果我们使用的实例对象做key 需要重写hashCode,不然可能发生不同的实例对象但是hashCode是一致的情况
 final int hash(Object k) {
		// hashSeed是我们上面讲的初始化hashSeed,
        int h = hashSeed;
        if (0 != h && k instanceof String) {
            return sun.misc.Hashing.stringHash32((String) k);
        }
        h ^= k.hashCode();
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
  }
  // 接下来我们得到hashCode之后,我们应该要去得到他所在table的index了
  static int indexFor(int h, int length) {
	  // 这个方法很简单,这个index方法也体现了用2的幂的好处,
	  // 因为table.length -1 刚好会使高位变0低位全变为1,这样“与”操作可以充分使用hash的离散性,且高位为0不会发生越界
      return h & (length-1);
  }
  // 找到index后我们就可以拿到哈希桶的头结点了,可以开始遍历查找是否有相同的key,当然这里又要提一下:
  // if (e.hash == hash && ((k = e.key) == key || key.equals(k))) 这个方法里面用到了equals,
  // 那么要求我们是实例对象不单要重写hashcode还要重写equals
  // 好了,我们来看一下最后一个方法  addEntry(hash, key, value, i);
  void addEntry(int hash, K key, V value, int bucketIndex) {
  		// 判断table里的元素是否达到阈值,并且该index是否是null,
  		// 比如说现在的size = 12 那第13个键值对过来时并且没有落到空桶就会满足扩容条件
        if ((size >= threshold) && (null != table[bucketIndex])) {
            //当且仅当table里的元素达到了阈值,并且table[bucketIndex] != null 时才进行扩容,扩容为以前大小的两倍
            resize(2 * table.length);
            hash = (null != key) ? hash(key) : 0;
            bucketIndex = indexFor(hash, table.length);
        }
		// 如果没有达到阈值则初始化
        createEntry(hash, key, value, bucketIndex);
  }
  // 扩容操作 ------------开始-----------
  void resize(int newCapacity) {
        Entry[] oldTable = table;
        int oldCapacity = oldTable.length;
        // 如果table已经到了最大值则赋值阈值为最大值,并且return
        if (oldCapacity == MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return;
        }
        Entry[] newTable = new Entry[newCapacity];
        // 转移,既然这里又一次用到了initHashSeedAsNeeded 这个方法,那我不得不去看一下了
        transfer(newTable, initHashSeedAsNeeded(newCapacity));
        table = newTable;
        threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
  }
  // 这个方法在第一次inflateTable的时候会进来,当时进来的时候 capacity = 16,hashSeed 默认为 0
  final boolean initHashSeedAsNeeded(int capacity) {
  		// currentAltHashing = false
        boolean currentAltHashing = hashSeed != 0;
        // sun.misc.VM.isBooted() 自测是true,但不知道具体是哪个因素影响他的赋值
        // Holder.ALTERNATIVE_HASHING_THRESHOLD 看下面解析可以的得出这个值还是Integer.MAX_VALUE
        // 所以 useAltHashing = false;
        boolean useAltHashing = sun.misc.VM.isBooted() &&
                (capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
        // false^false,"^" 异或(相同为false,不同为true),所以 switching  = false;
        boolean switching = currentAltHashing ^ useAltHashing;
        if (switching) {
            hashSeed = useAltHashing
                ? sun.misc.Hashing.randomHashSeed(this)
                : 0;
        }
        return switching;
  }
  // ALTERNATIVE_HASHING_THRESHOLD  这个值是在这里静态内部类里面赋值的
   private static class Holder {
        static final int ALTERNATIVE_HASHING_THRESHOLD;
        static {
        	// 自测返回是null 
            String altThreshold = java.security.AccessController.doPrivileged(
                new sun.security.action.GetPropertyAction(
                    "jdk.map.althashing.threshold"));
            int threshold;
            try {
                threshold = (null != altThreshold)
                        ? Integer.parseInt(altThreshold)
                        : ALTERNATIVE_HASHING_THRESHOLD_DEFAULT;
                if (threshold == -1) {
                    threshold = Integer.MAX_VALUE;
                }
                if (threshold < 0) {
                    throw new IllegalArgumentException("value must be positive integer.");
                }
            } catch(IllegalArgumentException failed) {
                throw new Error("Illegal value for 'jdk.map.althashing.threshold'", failed);
            }
            ALTERNATIVE_HASHING_THRESHOLD = threshold;
        }
  }
  // 好了,最后一个transfer,从上文可以得知 rehash 是false
  void transfer(Entry[] newTable, boolean rehash) {
        int newCapacity = newTable.length;
        for (Entry<K,V> e : table) {
            while(null != e) {
                Entry<K,V> next = e.next;
                // 如果要rehash则重新求一遍hash,当然key为null的hash值直接为0
                if (rehash) {
                    e.hash = null == e.key ? 0 : hash(e.key);
                }
                // 求得新的newTable索引
                int i = indexFor(e.hash, newCapacity);
                // 下面这这三步是赋值操作,有一点点需要理解的地方,叫头插法,建议画图跟着走一下就知道了
                e.next = newTable[i];
                newTable[i] = e;
                e = next;
            }
        }
  }
  // transfer 之后 table = newTable;
  // 赋值为新容量的 threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
  // 扩容操作 ------------结束-----------
  //回到put的最后一个方法 createEntry,就是初始化一个新的entry加入table[index]
  void createEntry(int hash, K key, V value, int bucketIndex) {
        Entry<K,V> e = table[bucketIndex];
        table[bucketIndex] = new Entry<>(hash, key, value, e);
        size++;
    }

到这里put 方法就写完了,写的比较细,可能看起来要比较集中精力,不要走神,那我们再来看下简单的方法 get(Object key)

    public V get(Object key) {
        if (key == null)
        	// 如果key为null上面我们讲过key为null的数据会存在table[0]上面,那这个方法就是从table[0]返回,不展开讲了
            return getForNullKey();
        // 根据key获取entry    
        Entry<K,V> entry = getEntry(key);
        return null == entry ? null : entry.getValue();
    }
    // 根据key获取entry  
    final Entry<K,V> getEntry(Object key) {
        if (size == 0) {
            return null;
        }
        // 兼容key为null的情况,求hash值
        int hash = (key == null) ? 0 : hash(key);
        // table[indexFor(hash, table.length)] 得到hash对应的哈希桶也就是index
        // 遍历
        for (Entry<K,V> e = table[indexFor(hash, table.length)]; e != null; e = e.next) {
            Object k;
            // 如果hash值相同,并且key也相等就返回
            if (e.hash == hash &&
                ((k = e.key) == key || (key != null && key.equals(k))))
                return e;
        }
        return null;
    }

get 方法很简单吧~,那到这里java 7 hashmap 已经分析的差不多了,我们来总结下java7hashmap的特点吧。
1、非线程安全,安全问题主要发生在扩容里面的那个rehash后面的那几行,可能会构成环形链表,导致get的时候发生死循环
2、允许空键和空值(但空键只有一个,且放在第一位,下面会介绍)
3、插入、获取的时间复杂度基本是 O(1)(前提是有适当的哈希函数,让元素分布在均匀的位置)

二、Java8 HashMap

首先我们讲讲java8 为什么要对hashmap做改动,因为通过从java7的源码分析我们可以看出java7是数组加单向链表的方式实现的,那么就有一个问题,就是如果存的key的hashcode的离散性不高那么很有可能都落在一个哈希桶(index)上面,这样链表的长度可能会很长,那么时间复杂度也可能出现o(N)的情况,显然这种情况的性能是不能被接受的,所以java8HashMap对这种情况作了优化,还是采用数组加单向链表的形式,但是当链表中元素的个数达到8个就会形成红黑树,话不多说,我们开始撸源码。
先看结构图

红黑树先看这篇文章吧,我自己也在写,但是还没写完:https://www.jianshu.com/p/b7dda385f83d

在这里插入图片描述
跟java7的hashmap对比,区别主要在于对于元素大于8的链表会变成红黑树,至于为什么选择8为临界点,我后面会进行分析,还有就是java7 使用的节点叫entry java8 叫node 当然属性一样,只是node可以变成treenode,好了,我们先来看下属性吧

// 默认数组最小容量 16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
// 最大数组容量2的30次方
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;
// 桶中结构转化为红黑树对应的数组的最小大小,如果当前容量小于它,就不会将链表转化为红黑树,而是用resize()代替
static final int MIN_TREEIFY_CAPACITY = 64;
// 存储元素的数组,总是2的幂
transient Node<k,v>[] table; 
// 存放具体元素的集
transient Set<map.entry<k,v>> entrySet;
// 存放元素的个数,注意这个不等于数组的长度。
transient int size;
// 每次扩容和更改map结构的计数器,给快速失败使用
transient int modCount;   
// 临界值 当实际节点个数超过临界值(容量*填充因子)时,会进行扩容
int threshold;
// 填充因子
final float loadFactor;

我们再来看看构造方法

public HashMap(int initialCapacity, float loadFactor) {
    // 初始容量不能小于0,否则报错
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity);
    // 初始容量不能大于最大值,否则为最大值
    if (initialCapacity > MAXIMUM_CAPACITY)
        initialCapacity = MAXIMUM_CAPACITY;
    // 负载因子不能小于或等于0,不能为非数字
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
        throw new IllegalArgumentException("Illegal load factor: " + loadFactor);
    // 初始化负载因子                                        
    this.loadFactor = loadFactor;
    // 这里跟jav7不一样,java7的threshold 是直接赋值为正确的值,而这里是先赋值为不小于initialCapacity的最近的2的次幂的值
    // 然后在第一次put的时候才赋值为正确的值,可以看到下面那些构造函数都没有对threshold 直接赋值的情况
    this.threshold = tableSizeFor(initialCapacity);   
} 
//指定初始容量
public HashMap(int initialCapacity) {
    this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
//默认构造函数
public HashMap() {
    // 初始化负载因子
    this.loadFactor = DEFAULT_LOAD_FACTOR; 
}
//HashMap(Map<? extends K>)型构造函数
public HashMap(Map<? extends K, ? extends V> m) {
    // 初始化负载因子
    this.loadFactor = DEFAULT_LOAD_FACTOR;
    // 将m中的所有元素添加至HashMap中,这个方法可以点进去看下,但是他和我们接下来要讲的api会有重叠的地方,我这里就不展开了
    putMapEntries(m, false);
}

构造方法介绍完毕,我们还是来看下最重要的api,来看下java8的hashmap到底是怎么实现的吧
我们先来讲下 最常用的 put(K key, V value)

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<K,V>[] tab; Node<K,V> p; int n, i;
    // 将 table 赋值给tab ,判断是否是空数组
    if ((tab = table) == null || (n = tab.length) == 0)
    	// 初始化数组赋值给tab并将length赋值给 n,resize见下面的分析(有点长)
        n = (tab = resize()).length;
    // 求得当前数据落在哪个index并且判断头结点是否==null
    if ((p = tab[i = (n - 1) & hash]) == null)
    	// == null 则是空桶,直接初始化一个新的头结点即可
        tab[i] = newNode(hash, key, value, null);
    else {
    	// 进入这个分支证明当前index上不为空,要么为链表要么为红黑树
        Node<K,V> e; K k;
        // 如果put的的数据跟头结点一样则将它赋值给e
        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) {
                	// 取得节点的next为null的,也就是最后一个节点,初始化节点加入
                    p.next = newNode(hash, key, value, null);
                    // 如果超过最大链表长度8 则转为红黑树
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        treeifyBin(tab, hash);
                    break;
                }
                // 如果链表中有跟put进来一样的值则跳出循环
                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;
    // 对size自增后判断是否超过阈值threshold,超过则再进行一次扩容
    if (++size > threshold)
        resize();
    afterNodeInsertion(evict);
    return null;
}
// 扩容方法
final Node<K,V>[] resize() {
	// 将table 赋值为oldTab
    Node<K,V>[] oldTab = table;
    // oldTab长度赋值给oldCap (老数组的容量)
    int oldCap = (oldTab == null) ? 0 : oldTab.length;
    // threshold赋值给 oldThr (老数组的threshold)
    int oldThr = threshold;
    // 初始化 newCap(新数组的容量),newThr(新数组的threshold)为0
    int newCap, newThr = 0;
    if (oldCap > 0) {
    	// 数组长度是否最大长度,大于则将 threshold 赋值为Integer.MAX_VALUE并返回oldTab
        if (oldCap >= MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return oldTab;
        }
        // 如果不大于最大长度则看左移一位后(也就是*2)是不是小于最大长度,并且原本长是不是大于默认初始化长度 16 
        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                 oldCap >= DEFAULT_INITIAL_CAPACITY)
            // 将 oldThr 左移一位赋值给 newThr,这里是数组长度>0的情况,那么oldThr 肯定已经是赋值过正确的值了
            newThr = oldThr << 1; // double threshold
    }
    else if (oldThr > 0) 
    	// 初始化 newCap  = oldThr,因为有可能构造方法传过值,但是这次过来时第一次初始化
        newCap = oldThr;
    else {
    	// 如果从未初始化则将DEFAULT_INITIAL_CAPACITY(16)赋值给newCap 
        newCap = DEFAULT_INITIAL_CAPACITY;
        // 赋值 newThr
        newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
    }
    if (newThr == 0) {
    	// 可能出现的情况就是构造函数初始化出现的情况,所以这里newThr 没有得到赋值,因为oldCap = 0
    	// 得到老的oldThr
        float ft = (float)newCap * loadFactor;
        // 查看newCap 是不是大于最大容量且oldThr是不是大于最大容量
        newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                  (int)ft : Integer.MAX_VALUE);
    }
    // 将新的threshold 赋值给全局threshold
    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> e;
            // 遍历oldCap的哈希桶,如果头结点不为空
            if ((e = oldTab[j]) != null) {
                // 将老数组的哈希桶置为空						
                oldTab[j] = null;
                //e.next == null 等于空那证明还没有形成链表
                if (e.next == null)
                	// 直接赋值
                    newTab[e.hash & (newCap - 1)] = e;
                // 如果e是treeNode,那证明已经形成红黑树
                else if (e instanceof TreeNode)
                    ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                else { // preserve order
                	// 进入这个分支则证明是该哈希桶上面形成的是链表
                    Node<K,V> loHead = null, loTail = null;
                    Node<K,V> hiHead = null, hiTail = null;
                    Node<K,V> next;
                    do {
                        next = e.next;
                        // 如果(e.hash & oldCap) == 0 则不会挪坑,就是在当前index里不变
                        if ((e.hash & oldCap) == 0) {
                            if (loTail == null)
                                loHead = e;
                            else
                                loTail.next = e;
                            loTail = e;
                        }
                        else {//hash到高部分即挪到原index+oldCap
                            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;
                    }
                }
            }
        }
    }
    return newTab;
}

好了,put(K key, V value) 已经只剩下最重要的一个篇幅了,那就是红黑树!我自己打算单独写一篇,所以写完之后我会给出链接供大家参考,这里暂时先不展开讲了,我们先进入下一个方法

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值