HashMap源码解析

基本概念:

  • jdk1.8的HashMap底层的数据结构是,数组+链表+红黑树,当我们存储元素的时候,如果存在hash冲突,链表的深度会不断加深,同时集合的容量会不断增加,当到达一个需要转化的点之后(如下),链表会转化成红黑树。
  1. 链表的深度达到8
  2. 集合的容量达到64
  • 当我们删除元素的时候,如果红黑树中元素的数量减小到6会转换成链表结构

成员变量

    /**
     * 默认的初始化容量
     */
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;

    /**
     * 最大的容量
     */
    static final int MAXIMUM_CAPACITY = 1 << 30;

    /**
     * 默认的扩容因子
     */
    static final float DEFAULT_LOAD_FACTOR = 0.75f;

    /**
     * 当链表深度到达8的时候转化成红黑树的点
     */
    static final int TREEIFY_THRESHOLD = 8;

    /**
     * 当红黑树中元素节点的值小于6的时候转换成链表
     */
    static final int UNTREEIFY_THRESHOLD = 6;

    /**
     * 当整个hash表中的元素到达64的时候才会进行树化
     */
    static final int MIN_TREEIFY_CAPACITY = 64;

    /**
     * 当前hash表的数组 也就是hash桶
     */
    transient Node<K, V>[] table;

    /**
     * 作为entrySet的缓存
     */
    transient Set<Entry<K, V>> entrySet;

    /**
     * 元素的数量
     */
    transient int size;

    /**
     * 迭代的次数,用于快速失败策略
     */
    transient int modCount;

    /**
     * 当前map的容量到达多少的时候进行扩容处理
     * threshold = capacity * loadFactor(下面那个参数) 扩容门槛
     */
    int threshold;

    /**
     * 扩容因子  用于计算容器扩容的点  默认是0.75
     */
    final float loadFactor;
  • 需要重点关注的几个成员变量是
    /**
     * 默认的扩容因子
     */
    static final float DEFAULT_LOAD_FACTOR = 0.75f;

    /**
     * 当链表深度到达8的时候转化成红黑树的点
     */
    static final int TREEIFY_THRESHOLD = 8;

    /**
     * 当红黑树中元素节点的值小于6的时候转换成链表
     */
    static final int UNTREEIFY_THRESHOLD = 6;

    /**
     * 当整个hash表中的元素到达64的时候才会进行树化
     */
    static final int MIN_TREEIFY_CAPACITY = 64;

    /**
     * 当前map的容量到达多少的时候进行扩容处理
     * threshold = capacity * loadFactor(下面那个参数) 扩容门槛
     */
    int threshold;

   /**
     * 扩容因子  用于计算容器扩容的点  默认是0.75
     */
    final float loadFactor;

两个底层的数据结构

  • 链表
    /**
     *  代表的是链表结构
     */
    static class Node<K, V> implements Entry<K, V> {
        final int hash;
        final K key;
        V value;
        Node<K, V> next;

      ....................
    }
  • 红黑树
    static final class TreeNode<K, V> extends LinkedHashMap.Entry<K, V> {
        TreeNode<K, V> parent;
        TreeNode<K, V> left;
        TreeNode<K, V> right;
        TreeNode<K, V> prev;
        boolean red;

    .......
    }

构造函数

  • 无参构造函数
    /**
     * 空参构造方法,全部使用默认值。
     */
    public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }
  • 传入初始化容量的构造函数
    /**
     * 调用HashMap(int initialCapacity, float loadFactor)构造方法,传入默认装载因子。
     */
    public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);《1》
    }
  1. 《1》调用了this(initialCapacity, DEFAULT_LOAD_FACTOR);方法
    /**
     * 判断传入的初始容量和装载因子是否合法,
     * 并计算扩容门槛,扩容门槛为传入的初始容量往上取最近的2的n次方。
     */
    public HashMap(int initialCapacity, float loadFactor) {

        // 检查传入的初始容量是否合法
        if (initialCapacity < 0)   throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity);

        //  这句话表明hashmap的最大值就是MAXIMUM_CAPACITY
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;

        // 检查装载因子是否合法
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " + loadFactor);

        // <1>设置装载因子
        this.loadFactor = loadFactor; // 0.75

        // <2>计算扩容门槛 扩容门槛为传入的初始容量往上取最近的2的n次方。
        this.threshold = tableSizeFor(initialCapacity);
    }
  • <1>自定义容量的构造函数设置的默认装载因子(用于扩容门槛计算)
  • <2>扩容门槛为当前容量向上取整最近的2的n次方
  • <3>留一个疑问:这个扩容门槛都比当前容量大了????mmpd的 这放数据放多了咋玩? 还好没创建集合呢 。 后面真正创建集合的时候会有答案。

put添加元素

   /**
     添加元素的入口。
     */
    public V put(K key, V value) {
        // 《2》 putVal 真正放置元素的方法
        return putVal(
                hash(key),  // 《1》调用hash(key)计算出key的hash值
                key,
                value,
                false,
                true);
    }
  • 《1》调用hash(key)计算出key的hash值

    /**
     *    对key进行hash运算
     *    为了更加的提高他的分散值 对他进行低16位和高16位的异或操作
     */
    static final int hash(Object key) {
        int h;
        // 低16位和高16位进行异或运算 提高利用率
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
  • 为了让元素进入到不同的链表中,增加元素的分散比率,这里对key的hashCode值做了一个高16位和低16位的异或操作
  • 这里还有一个疑问hashcode取出来之后这么大,怎么就放到有数的这几个hash桶中的呢? 大家都能想到办法就是取余。真是这么搞的嘛?

 

  • 《2》 putVal 真正放置元素的方法
     /**
     * 存放元素的值
     */
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {

        // 生明hash表
        Node<K, V>[] tab;

        // 声明单个hash元素
        Node<K, V> p;

        int n, //数组的长度
                i; //数组的位置

        // 如果当前hash桶的长度为0
        if ((tab = table) == null || (n = tab.length) == 0)

 //<1>调用resize方法进行初始化
            n = (tab = resize()).length;
            
 //<2>算出当前元素在hash表中的位置     
        /*如果当前位置一个元素都没有*/
        if ((p = tab[i = (n - 1) & hash]) == null)
            // 新建一个节点放到hash桶中
            tab[i] = newNode(hash, key, value, null);

        // 如果当前桶中已经存在元素了
        else {
            Node<K, V> e;
            K k;

            // 如果桶中第一个元素的key与待插入元素的key相同   hash表的第一个位置
            if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
                //保存到e中用于后续修改value值
                e = p;

            // 如果第一个元素是树节点
            else if (p instanceof TreeNode)
                // 调用树节点的putTreeVal插入元素
                e = ((TreeNode<K, V>) p).putTreeVal(this, tab, hash, key, value);

            // 不是树节点 而且不在hash桶的第一个位置,那就是在当前hash桶的元素里面 
            //进去找(循环遍历找)
            else {
                for (int binCount = 0; ; ++binCount) {

                    // 如果链表遍历完了都没有找到相同key的元素,
                    if ((e = p.next) == null) {

                        // 说明该key对应的元素不存在,则在链表最后插入一个新节点
                        p.next = newNode(hash, key, value, null);


 
                        // 如果插入新节点后链表长度大于8,则判断是否需要树化,
                        //因为第一个元素没有加到binCount中,所以这里-1
                        if (binCount >= TREEIFY_THRESHOLD - 1)
  //<3>进行树化
                            treeifyBin(tab, hash);
                        break;
                    }

                    // 如果待插入的key在链表中找到了,则退出循环 
                    // 且当前p保存到e中用于后续修改value值  也就是替换
                    if (e.hash == hash && ((k = e.key) == key 
                            || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }

            // 如果找到了对应key的元素
            if (e != null) {

                // 记录下旧的值
                V oldValue = e.value;

                // 判断是否需要替换旧值
                if (!onlyIfAbsent || oldValue == null)
                    // 替换旧值为新的值
                    e.value = value;

                // 在节点被访问后做点什么事,在LinkedHashMap中用到 实现为null
                afterNodeAccess(e);

                // 替换完毕之后返回旧的值
                return oldValue;
            }
        }
        // 增加修改次数
        ++modCount;

        // 给当前size++ 并且判断当前容量是否超值 是否需要扩容
        if (++size > threshold)
            // 进行扩容处理
            resize();

        // 在节点插入后做点什么事,在LinkedHashMap中用到
        afterNodeInsertion(evict);

        //没有找到元素返回为null 只有在put新的值的时候才会返回为null
        return null;
    }
  • 总结流程:
  • (1)计算key的hash值;
  • (2)如果桶(数组)数量为0,则初始化桶;
  • (3)如果key所在的桶没有元素,则直接插入(如果是初始化的那就是没值咯,直接放呗);
  • (4)如果key所在的桶中的第一个元素的key与待插入的key相同,说明找到了元素,转后续流程(9)处理;
  • (5)如果第一个元素是树节点,则调用树节点的putTreeVal()寻找元素或插入树节点;
  • (6)如果不是以上三种情况,则遍历桶对应的链表查找key是否存在于链表中;
  • (7)如果找到了对应key的元素,则转后续流程(9)处理;
  • (8)如果没找到对应key的元素,则在链表最后插入一个新节点并判断是否需要树化;
  • (9)如果找到了对应key的元素,则判断是否需要替换旧值,并直接返回旧值;
  • (10)如果插入了元素,则数量加1并判断是否需要扩容;

 

  • <1>调用resize方法进行初始化,留在后面进行专门讲解
  • <2>算出当前元素在hash表中的位置

if ((p = tab[i = (n - 1) & hash]) == null)中的(n - 1) & hash是真正计算存储到数组的位置

假设我们的数组的容量n是16而且要存的是20  二进制(‭00010100)

16-1 等于15 二进制( 01111)‬

16                   00010100
15                         01111
结果                      00100

最后的结果就是  00100 也就是4 这不是和取模运算一样嘛? 不过&运算比%运算效率高哦。

其实,(n - 1) & hash 就是取当前hash值和数组容量的余数操作之不说是算法不一样而已。

这里要提一点的是(有没有发现hashMap的容量都是2的次方 发现了新大陆 居然和这个hash值的运算合起来了,写hashMap这个大佬真是爸爸)

  • <3>进行树化 treeifyBin(tab, hash); 留在后面进行专门讲解

来了来了重头戏(黑帮老大级别)resize方法(扩容+初始化)

  final Node<K, V>[] resize() {

        // 记录下旧的hash桶
        Node<K, V>[] oldTab = table;

        // 记录下原来hash桶的是数量
        int oldCap = (oldTab == null) ? 0 : oldTab.length;

        // 记录下原容器需要扩容的点             这个是采用非默认构造函数之后创建出来的
        int oldThr = threshold;

        // 声明新的hash桶的长度 和 新容器需要进行扩容的点
        int newCap, newThr = 0;

// 《1》这个是扩容
        // 如果当前hash桶中有值则不是进行初始化
        if (oldCap > 0) {

            // 如果当前hash桶的数量大于等于最大容量
            if (oldCap >= MAXIMUM_CAPACITY) {

                // 则扩容因子为int类型的最大整数 不在进行扩容
                threshold = Integer.MAX_VALUE;

                // 不进行扩容 返回旧的hash桶
                return oldTab;

                // 如果旧容量的两倍小于最大容量并且旧容量大于默认初始容量(16),则容量扩大为两部,扩容门槛也扩大为两倍
            } else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY)
                newThr = oldThr << 1;   // 这里oldThr 最少也是2

            // 使用非默认构造方法创建的map,第一次插入元素会走到这里
//《2》这个是初始化(非默认构造方法的哦)
        } else if (oldThr > 0)
            // 如果旧容量为0且旧扩容门槛大于0,则把新容量赋值为旧门槛
            newCap = oldThr;              // 这里newThr可能为0  就是这里了  因为这里没设置值。

//《3》这个是初始化(默认构造方法的哦)
            // 调用默认构造方法创建的map,第一次插入元素会走到这里
        else {
            newCap = DEFAULT_INITIAL_CAPACITY; //声明新的容量
            newThr = (int) (DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); // 扩容门槛= 默认的装载因子 * 默认的初始容量为16   这里newThr就不可能为0
        }

// 《4》 这个是计算扩容因子(非默认构造方法才会走的哦)
        // 这里就是hashmap采用非默认构造参数走的方法
        if (newThr == 0) {
            // 这里面的newCap = 扩容门槛 前面有赋值进来
            float ft = (float) newCap * loadFactor;   // ft = 扩容门槛*装载因子(扩容门槛是采用非默认构造函数之后创建出来的 装载因子是采用非默认构造函数传递进来的)

            // 如果旧的扩容门槛小于最大的容量,而且 新的扩容门槛小于最大容量 则把新的扩容门槛赋值给当前扩容门槛 否则则以后都不进行扩容
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float) MAXIMUM_CAPACITY ? (int) ft : Integer.MAX_VALUE);
        }


        // 把旧的扩容门槛赋值给新的扩容门槛
        threshold = newThr;

        @SuppressWarnings({"rawtypes", "unchecked"})
        // 扩容之后的hash数组
        Node<K, V>[] newTab = (Node<K, V>[]) new Node[newCap];

        table = newTab; // 把新数组赋值给当前成员变量 hash桶

// 这里就是真的走的扩容的逻辑 初始化不走
        if (oldTab != null) { // 如果旧的数组不等于null

            // 循环遍历数组中的值
            for (int j = 0; j < oldCap; ++j) {

                Node<K, V> e;
                // 把值取出来到e中 并且判断当前是不是null               如果是null 那么就代表当前链表根本就没有值在这
                if ((e = oldTab[j]) != null) {

                    // 如果不为null 把当前置空 便于垃圾回收机制回收
                    oldTab[j] = null;

                    // 如果e的下一个元素为null                            代表的就是当前只有一个值在这 直接原算法 因为数组长度扩大了 所以要重新计算位置
                    if (e.next == null)

                        //则把值放到新的数组的位置
                        newTab[e.hash & (newCap - 1)] = e;

                        //如果下面是红黑树 那么就直接把下面split大散掉
                    else if (e instanceof TreeNode)
                        ((TreeNode<K, V>) e).split(this, newTab, j, oldCap);
//  《5》  这里就是有深度的链表需要重新定位在数组中的位置
                       // 那么就只剩下链表了                            这里就是有深度的链表需要重新定位在数组中的位置
                    else {

                        // 低位头和尾巴      头记录了第一个元素的位置 也就是hash值   尾用于一个一个往后加元素   加到最后头就代表了整个元素
                        Node<K, V> loHead = null, loTail = null;

                        // 高位头和尾巴
                        Node<K, V> hiHead = null, hiTail = null;
                        Node<K, V> next;

                        // 不断遍历链表
                        do {
                            next = e.next;

//  《6》  有一个算位置的鬼东西 mmp的
                            if ((e.hash & oldCap) == 0) {
                                // 没有尾巴
                                if (loTail == null)
                                    // 设置头
                                    loHead = e;
                                else
                                    //设置尾巴的下一个元素
                                    loTail.next = e;

                                // 设置当前尾巴  当第一次进来的时候是相同的跟头  就代表者头代表了尾向后添加的所有元素(也就是每一个next代表的一串的值)
                                loTail = e;
                            } else {
                                if (hiTail == null)
                                    hiHead = e;
                                else
                                    hiTail.next = e;
                                hiTail = e;
                            }
                        } while ((e = next) != null);


                        // 如果存放低位置的节点不是null 那么就放到低位置
                        if (loTail != null) {
                            loTail.next = null;
                            newTab[j] = loHead;
                        }

                        // 如果存放高位置的节点不是null 那么就放到高位置
                        if (hiTail != null) {
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        return newTab;
    }
  • 流程总结
  • (1)如果使用是默认构造方法,则第一次插入元素时初始化为默认值,容量为16,扩容门槛为12;
    else {
            newCap = DEFAULT_INITIAL_CAPACITY; //声明新的容量
            newThr = (int) (DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); 
            // 扩容门槛= 默认的装载因子 * 默认的初始容量为16   
        }
  • (2)如果使用的是非默认构造方法,则第一次插入元素时初始化容量等于扩容门槛,扩容门槛在构造方法里等于传入容量向上最近的2的n次方;

1、采用传入初始值的时候的构造函数得到的扩容因子为,初始容量向上取最近的2次方

        // 计算扩容门槛 扩容门槛为传入的初始容量往上取最近的2的n次方。
        this.threshold = tableSizeFor(initialCapacity);

2、把旧的扩容门槛的值赋值给新容器中的容量(到现在扩容门槛==当前容器容量了)

   else if (oldThr > 0)
            // 如果旧容量为0且旧扩容门槛大于0,则把旧的扩容门槛的值赋值给新容器中的容量
            newCap = oldThr;
            //  这里可是没有设置扩容门槛的哦

3、在重新计算一下当前扩容门槛(新容量*装载因子)

 // 这里就是hashmap采用非默认构造参数走的方法
        if (newThr == 0) {
            // 这里面的newCap = 扩容门槛 前面有赋值进来
            float ft = (float) newCap * loadFactor;   
        // ft = 新容量*装载因子
        // (扩容门槛是采用非默认构造函数之后创建出来的 
        //装载因子是采用非默认构造函数传递进来的)

            // 如果旧的扩容门槛小于最大的容量,而且 新的扩容门槛小于最大容量 
            //则把新的扩容门槛赋值给当前扩容门槛 否则则以后都不进行扩容
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float) MAXIMUM_CAPACITY ? 
            (int) ft : 
            Integer.MAX_VALUE);
        }
  • (3)如果旧容量大于0,则新容量等于旧容量的2倍,但不超过最大容量2的30次方,新扩容门槛为旧扩容门槛的2倍;
// 《1》这个是扩容
        // 如果当前hash桶中有值则不是进行初始化
        if (oldCap > 0) {

            // 如果当前hash桶的数量大于等于最大容量
            if (oldCap >= MAXIMUM_CAPACITY) {

                // 则扩容因子为int类型的最大整数 不在进行扩容
                threshold = Integer.MAX_VALUE;

                // 不进行扩容 返回旧的hash桶
                return oldTab;

                // 如果旧容量的两倍小于最大容量并且旧容量大于默认初始容量(16),
                //则容量扩大为两部,扩容门槛也扩大为两倍
            } else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && 
                oldCap >= DEFAULT_INITIAL_CAPACITY)

                newThr = oldThr << 1;   
        }
  • (4)创建一个新容量的桶;
        // 扩容之后的hash数组
        Node<K, V>[] newTab = (Node<K, V>[]) new Node[newCap];

        table = newTab; // 把新数组赋值给当前成员变量 hash桶
  • (5)搬移元素,原链表分化成两个链表,低位链表存储在原来桶的位置,高位链表搬移到原来桶的位置加旧容量的位置;
//  《5》  这里就是有深度的链表需要重新定位在数组中的位置
                        // 那么就只剩下链表了                            
                        //这里就是有深度的链表需要重新定位在数组中的位置
                    else {

                        // 低位头和尾巴     
                        // 头记录了第一个元素的位置 也就是hash值   
                        // 尾用于一个一个往后加元素   加到最后头就代表了整个元素
                        Node<K, V> loHead = null, loTail = null;

                        // 高位头和尾巴
                        Node<K, V> hiHead = null, hiTail = null;
                        Node<K, V> next;

                        // 不断遍历链表
                        do {
                            next = e.next;
                       
//  《6》  有一个算位置的鬼东西 mmp的
                            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);


                        // 如果存放低位置的节点不是null 那么就放到低位置
                        if (loTail != null) {
                            loTail.next = null;
                            newTab[j] = loHead;  // 低位数组
                        }

                        // 如果存放高位置的节点不是null 那么就放到高位置
                        if (hiTail != null) {
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;  //加上原数组长度到达高位数组 
                        }
                    }

1、当我们扩容之后元素需要重新定位元素中的位置通过(e.hash & oldCap) == 0)  算出元素是在高位数组还是低位数组中

例子1:假设当前数组长度为16要扩容到32,并且hashMap中存放了数据54

原来未扩容之前的数组的位置
    110110       54
     01111       16-1=15
      0110       6
  原数组的位置 6

现在扩容之后按照原算法设置位置
    110110    54
    011111    32-1=31
     10110    22
 最后得到的结果22

现在扩容之后的新算法数组的位置
     10110
     10000
     10000
不等于0 代表的就是高16位的地方  原来数组的长度+原来数组的位置 =22

最后的结果省去一次计算
 

2、重新排列时候的两个链表是怎么分化的?

                        // 低位头和尾巴     
                        // 头记录了第一个元素的位置 也就是hash值   
                        // 尾用于一个一个往后加元素   加到最后头就代表了整个元素
                        Node<K, V> loHead = null, loTail = null;

                        // 高位头和尾巴
                        Node<K, V> hiHead = null, hiTail = null;
    
                        .................

                          // 如果存放低位置的节点不是null 那么就放到低位置
                        if (loTail != null) {
                            loTail.next = null;
                            newTab[j] = loHead;
                        }

                        // 如果存放高位置的节点不是null 那么就放到高位置
                        if (hiTail != null) {
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
  • 头和尾记录了相同的地址值
  • 每次循环出一个新元素放到(高位或者低位)尾的下一个元素(用((e.hash & oldCap) == 0)计算低位还是高位)
  • 最后低位链表放置到原位置不变(放置头中存放的地址值),高位链表的位置等于原位置+原数组的容量(放置头中存放的地址值)

get方法(没啥好说的直接看就能懂)

    /**
     * 获取元素值得方法
     */
    public V get(Object key) {
        Node<K, V> e;
        /*调用的是下面的这个方法*/
        // 原方法获取hash值
        return (e = getNode(hash(key), key)) == null ? null : e.value;
    }
/**
     *   获取key代表的链表中的节点
     */
    final Node<K, V> getNode(int hash, Object key) {
        Node<K, V>[] tab;
        Node<K, V> first, e;
        int n;
        K k;

        // 如果桶的数量大于0并且待查找的key所在的桶的第一个元素不为空                                                  这就证明可能在当前链表中
        if ((tab = table) != null && (n = tab.length) > 0 && (first = tab[(n - 1) & hash]) != null) {
            // 如果第一个元素的hash值相等 并且 第一个元素的key的地址值或者内容值相等那么久直接返回                   // 检查第一个元素是不是要查的元素,如果是直接返回
            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;
    }

树化的方法treeifyBin(关于红黑树的部分比较复杂,还没咋看,简单看一下关键点)

    /**
     *   执行树化的逻辑
     */
    final void treeifyBin(Node<K, V>[] tab, int hash) {
        int n, index;
        Node<K, V> e;

        // 如果桶数量小于64,直接扩容而不用树化
        // 因为扩容之后,链表会分化成两个链表,达到减少元素的作用
        // 当然也不一定,比如容量为4,里面存的全是除以4余数等于3的元素
        // 这样即使扩容也无法减少链表的长度
        if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
            resize();
        else if ((e = tab[index = (n - 1) & hash]) != null) {
            TreeNode<K, V> hd = null, tl = null;
            do {
                // 把所有节点换成树节点
                TreeNode<K, V> p = replacementTreeNode(e, null);
                if (tl == null)
                    hd = p;
                else {
                    p.prev = tl;
                    tl.next = p;
                }
                tl = p;
            } while ((e = e.next) != null);

            // 如果进入过上面的循环,则从头节点开始树化
            if ((tab[index] = hd) != null)
                hd.treeify(tab);
        }
    }
  • 总结:
  •  如果桶数量小于64,直接扩容而不用树化
  • 因为扩容之后,链表会分化成两个链表,达到减少元素的作用
  • 当然也不一定,比如容量为4,里面存的全是除以4余数等于3的元素,这样即使扩容也无法减少链表的长度
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值