JDK源码学习之路数据结构之-HashMap

 

在讲解具体的源码之前,先来对其jdk的介绍做一个翻译,下面是jdk源码对其介绍:

       HashMap作为Map接口的实现,提供了所有的可选择的映射的操作,大致上HaspMap和HashTable相等(不是线程安全和允许空值除外),HashMap对map的key的顺序没做出任何保证,特别是不会保证顺序恒久不变。

      假定hash方法可以将元素适当的散列在各个桶之间,那么hashmap的get()和 put()方法可以提供常数时间的表现,Hashmap的迭代集合的时间和其容量以及键值对的数量是成正比的,所以,如果迭代性能比较重要,那么不要讲初始容量设置的太高(或者加载因子太低)是非常重要的。


        默认的加载因子(0.75)在空间和时间上之间做了一个很好的权衡,空间上在增加的越多,就会增加查找代价(这一点在hashmap的许多操作方法都有体现,例如get put),
在设置初始容量的时候,我们所期望的节点的数量和加载因子应该被考虑在内,所以为了最小化扩容的次数,如果初始化的容量比最大节点数量除以加载因子的值要大,那么扩容将不再发生
        如果HashMap中存储大量的键值对,使用一个足够大的容量去创建去存储键值对要比扩容更高效,
        但是有一点需要注意,HashMap不是线程安全的,如果多个线程同时访问HashMap,而且至少有一个线程在结构上修改HashMap,在外部必须要被同步,所谓的在结构上修改是指增加、删除一个或者多个键值对
并不是指修改一个已经存在的键的值

 

HashMap结构图:

 

 在jdk1.7中,Hashmap的原理是基于哈希表的,哈希表是数组和链表的结合,也可以说是一个链表的数组,数组的每一个元素都是一个链表,通过数组下标,我们可以快速定位到某个元素所在的桶,然后再从链表头部通过比较key和value,定位到元素,链表是为了解决哈希碰撞的问题,这里可以看出,如果链表过长,则会降低检索效率。当元素个数达到扩容阈值(数组容量*加载因子)的时候,HashMap会进行扩容操作,将原先数组长度扩大为两倍的长度,对现有元素进行重新哈希

 

  • 2 成员变量
//默认的初始容量,大小必须是2的幂次方,其实数组的长度
static final int DEFAULT_INITIAL_CAPACITY = 16;

//最大容量,如果一个更大的值通过构造函数设置的话,那么就是用这个最大容量

//数组的最大长度
static final int MAXIMUM_CAPACITY = 1 << 30;
//默认的加载因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;
//用来存储链表的数组,必要时会发生扩容,大小必须是2的倍数
transient Entry<K,V>[] table;

//键值对的数量,hashmap的大小
transient int size;

//扩容的阈值(当前容量 * 加载因子),大于等于该值的时候会发生扩容
int threshold;

//加载因子
final float loadFactor;

//hashmap被修改的次数
transient int modCount;
//在键为string的时候,且使用可替代的hash算法的情况下,hashmap默认的扩容阈值,可替代的hash算法减少了由于薄弱的hash算法产生的hash冲突,这个值可以通过dk.map.althashing.threshold来覆盖,
当jdk.map.althashing.threshold为1的时候,一定会使用可替代hash,-1从不会使用
static final int ALTERNATIVE_HASHING_THRESHOLD_DEFAULT = Integer.MAX_VALUE;

 

 /**
     * holds values which can't be initialized until after VM is booted.

     * Hashmap定义的内部类,定义了一些在虚拟机启动之前不能被初始化的值
     */
    private static class Holder {

            // Unsafe mechanics
        /**
         * Unsafe utilities
         */
        static final sun.misc.Unsafe UNSAFE;

        /**
         * Offset of "final" hashSeed field we must set in readObject() method.
         */
        static final long HASHSEED_OFFSET;

        /**
         * Table capacity above which to switch to use alternative hashing.
         */
        static final int ALTERNATIVE_HASHING_THRESHOLD;

        static {
            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;

                // disable alternative hashing if -1
                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;

            try {
                UNSAFE = sun.misc.Unsafe.getUnsafe();
                HASHSEED_OFFSET = UNSAFE.objectFieldOffset(
                    HashMap.class.getDeclaredField("hashSeed"));
            } catch (NoSuchFieldException | SecurityException e) {
                throw new Error("Failed to record hashSeed offset", e);
            }
        }
    }

 

//Entry类,实现了Map.Entry接口
static class Entry<K,V> implements Map.Entry<K,V> {
    final K key;//节点的key
    V value;//节点的value
    Entry<K,V> next;//节点的后继节点
    int hash;//hash值

    /**
     * 构造函数,创建一个节点
     */
    Entry(int h, K k, V v, Entry<K,V> n) {
        value = v;
        next = n;
        key = k;
        hash = h;
    }
    //获取key
    public final K getKey() {
        return key;
    }
    //获取value
    public final V getValue() {
        return value;
    }
    //设置value,返回旧的value
    public final V setValue(V newValue) {
        V oldValue = value;
        value = newValue;
        return oldValue;
    }
    //判断两个节点是否相等,由此可见,两个节点相等的条件是:
    
    public final boolean equals(Object o) {
        if (!(o instanceof Map.Entry))
            return false;
        Map.Entry e = (Map.Entry)o;
        Object k1 = getKey();
        Object k2 = e.getKey();
        if (k1 == k2 || (k1 != null && k1.equals(k2))) {
            Object v1 = getValue();
            Object v2 = e.getValue();
            if (v1 == v2 || (v1 != null && v1.equals(v2)))
                return true;
        }
        return false;
    }

    public final int hashCode() {
        return (key==null   ? 0 : key.hashCode()) ^
               (value==null ? 0 : value.hashCode());
    }

    public final String toString() {
        return getKey() + "=" + getValue();
    }

    /**
     * This method is invoked whenever the value in an entry is
     * overwritten by an invocation of put(k,v) for a key k that's already
     * in the HashMap.
     */
    void recordAccess(HashMap<K,V> m) {
    }

    /**
     * This method is invoked whenever the entry is
     * removed from the table.
     */
    void recordRemoval(HashMap<K,V> m) {
    }
}
  • 3 添加元素

 put方法源码解析:

/**
     * Associates the specified value with the specified key in this map.
     * If the map previously contained a mapping for the key, the old
     * value is replaced.
     *
     * @param key key with which the specified value is to be associated
     * @param value value to be associated with the specified key
     * @return the previous value associated with <tt>key</tt>, or
     *         <tt>null</tt> if there was no mapping for <tt>key</tt>.
     *         (A <tt>null</tt> return can also indicate that the map
     *         previously associated <tt>null</tt> with <tt>key</tt>.)
     */
    public V put(K key, V value) {
        if (key == null)
            //如果添加的key为null,那么执行putForNullKey方法。
            return putForNullKey(value);
        //获取hash值
        int hash = hash(key);
        //根据hash值和数组长度计算该元素在桶中的位置
        int i = indexFor(hash, table.length);
        //通过下标快速定位到元素要添加的桶的位置,遍历该下标位置的链表,判断条件是如果该处节点不为空,如果hash值相等且key相等,则将vlaue替换为新的value,返回旧的value.否则就是要添加的该key不在hashmap中存在
        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;
            }
        }
        //如果添加的key不在map中存在,那么意味着要调用addEntry方法创建一个新的节点,所以modCount要+1,对hashmap的修改次数加1
        modCount++;
        //该方法会首先判断是否需要进行扩容操作,扩容操作的条件:
        1 元素数量大于扩容阈值
        2 下标处元素不为空
        同时满足这两个条件则进行扩容操作 
     
        该方法会创建一个新的节点,并将其指针指向位于下标的元素,将要添加的元素设置为下标处元素,也就是说将添加的新元素设置为链表的头结点
        addEntry(hash, key, value, i);
        return null;
    }

    

 

putFornNullKey方法源码解析


 private V putForNullKey(V value) {
       //直接遍历数组下标为0的节点,如果该下标处节点不为空,遍历这个链表,如果存在key为null的节点,那么替换旧的value,且返回旧的value 
       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;
            }
        }
        //如果0下标处无链表,或者有链表但是不存在key为null的节点,那么就要创建添加节点
        modCount++;
        addEntry(0, null, value, 0);
        return null;
    }

 


hash方法,根据这个方法计算key的哈希值

  /**
     * Retrieve object hash code and applies a supplemental hash function to the
     * result hash, which defends against poor quality hash functions.  This is
     * critical because HashMap uses power-of-two length hash tables, that
     * otherwise encounter collisions for hashCodes that do not differ
     * in lower bits. Note: Null keys always map to hash 0, thus index 0.
     */
    final int hash(Object k) {
        int h = 0;
        //是否使用替代的哈希函数,如果useAltHashing为true,则使用替代的哈希函数
        if (useAltHashing) {
            //如果key是string类型的,则使用备选的哈希函数,否则返回hashseed和key的哈希码异或后计算得到的哈希值
            if (k instanceof String) {
                return sun.misc.Hashing.stringHash32((String) k);
            }
            
            h = hashSeed;
        }

        h ^= k.hashCode();

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

 

 

//下面看下resize()扩容方法

void resize(int newCapacity) {

    Entry[] oldTable = table;//获取当前Entry数组
    int oldCapacity = oldTable.length;//当前数组长度
    if (oldCapacity == MAXIMUM_CAPACITY) {//如果当前数组的长度已经达到最大容量,则不进行扩容,且将扩容阈值设置为最大值,避免下次再次调用扩容方法
        threshold = Integer.MAX_VALUE;
        return;
    }
    //定义一个新的数组
    Entry[] newTable = new Entry[newCapacity];
    boolean oldAltHashing = useAltHashing;
    useAltHashing |= sun.misc.VM.isBooted() &&
            (newCapacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
    boolean rehash = oldAltHashing ^ useAltHashing;
    //transfer方法将当前数组上的节点转移到新的数组中
    transfer(newTable, rehash);
    //转移完成后,将table变量设置为新的数组
    table = newTable;
    //重新设置扩容阈值,这个阈值是新的容量*加载因子和最大值+1之间的最大值
    threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}
Transfer方法:
作用: 将所有的节点都转移到新的数组上去
/**
 * Transfers all entries from current table to newTable.
 */
void transfer(Entry[] newTable, boolean rehash) {
    //获取新数组的length 
    int newCapacity = newTable.length;
    //遍历数组中的各个链表元素
    for (Entry<K,V> e : table) {
         //while循环判断条件是:当该链表不为空
         while(null != e) {
            //获取当前节点的下个节点
            Entry<K,V> next = e.next;
            //如果需要重新hash,则重新计算hash值
            if (rehash) {
                e.hash = null == e.key ? 0 : hash(e.key);
            }
            //根据hash值和新数组的大小计算数组下标  
            int i = indexFor(e.hash, newCapacity);
            //当前节点指向该数组下标的节点元素 
            e.next = newTable[i];
            //然后将当前下标的元素设置为当前节点,e为下个节点
            newTable[i] = e;
            e = next;
        }
    }
}

根据transfer方法来看,在进行扩容后,原先的一条链表在新的hashmap中顺序发生了倒置 

 

//putForNullKey()方法

    当添加空的key的时候,首先遍历Entry<K,V>数组的第一个元素,如果这个链表中有key为null的节点,那么就将其value设置为null,返回旧的value,如果不存在,则电泳addEntry()方法添加新的节点,

private V putForNullKey(V value) {
    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++;
    addEntry(0, null, value, 0);
    return null;
}

一 添加元素的过程

1  key为null的情况

     如果元素的key为null,则调用putForNullKey方法进行处理:

     1.1  如果该下标处元素不为null,不会对key计算哈希值进而计算key的下标,而是直接遍历数组下标为0的位置的链表,在遍历过程中,如果有key为null的元素,则将该处元素的value修改设置为要添加的value,返回旧的value.

     1.2  如果该下标处元素为null或者未找到key为null的元素,如果未达到扩容条件,则使用所给key和value构造一个新的元素,并将该该下标处的元素设置为这个新元素,再将这个新元素的next指针指向原来该下标处的旧元素.

      备注:

      如果在添加元素的过程中,可能发生扩容操作,该操作会在后面进行介绍. 

2 key不为null的情况

       如果添加的key值不为null , 则会先计算key的哈希值 , 再根据hash和数组长度计算key的下标 ,  如果该下标处元素不为null ,

说明存在哈希冲突 , 此时需要遍历链表判断当前key是否已经在HashMap中存在 , 然后遍历该下标处所存链表 , 如果元素的hash值和要添加的元素的hash值相等且key值相等 , 则覆盖旧元素的value , 设置为新的value , 返回旧的元素

        如果该链表中不存在该key 或者 如果该下标处元素为null , 说明还未添加元素(不存在哈希冲突) ,这两种情况下 则根据key和value 构造 新的节点元素 , 将size属性加1 ,如果未达到扩容条件(也就是size小于数组长度乘以) , 则直接 添加到该下标处 ,并将next指针指向原来该下标处的旧元素或者null(该下标处元素为null).

        由此可见 , 对于添加HashMap不存在的key , 如果不发生扩容操作 , HashMap会将该元素放在链表的头部. 

二  扩容

    1 扩容的作用     

     扩容发生在添加元素的过程中 , 这里所说的添加 , 并不等同于put , 因为如果要添加的key值已经存在于HashMap中,那么此时在调用put方法的时候,会将旧的value值替换更新为 新的value , 当更新成功后 , 会直接返回 , 此时并不会改变属性值size ,当然也就不会触发扩容发生的条件.下面看下扩容操作发生的条件:

扩容操作的方法是在addEntry方法中 , 扩容条件为 :

1  当前HashMap中元素的个数 大于扩容阈值(扩容阈值=数组长度乘以加载因子) 

2  根据新值的key计算出的下标处的元素不为空,也就是在计算出的下标处添加元素的时候出现了哈希冲突

 看下面这段代码 , 当元素数量大于扩容阈值 且 该下标处元素不为空(发生了哈希冲突) , 则调用扩容方法  ,扩容后 ,

 HashMap容量变为原来2倍 , 然后重新计算key的哈希值 , 进而根据哈希值计算下标 , 然后调用createEntry方法 创建新的元素

 添加到链表头部。

void addEntry(int hash, K key, V value, int bucketIndex) {

    if ((size >= threshold) && (null != table[bucketIndex])) {
        resize(2 * table.length);
        hash = (null != key) ? hash(key) : 0;
        bucketIndex = indexFor(hash, table.length);
    }

    createEntry(hash, key, value, bucketIndex);
}

2 如何完成扩容

void resize(int newCapacity) {
    //旧数组的引用 
    Entry[] oldTable = table;
    //旧数组的长度
    int oldCapacity = oldTable.length;
    //如果容量已经达到了上限,则将扩容阈值设置为最大值,立即返回
    if (oldCapacity == MAXIMUM_CAPACITY) {
        threshold = Integer.MAX_VALUE;
        return;
    }
    //根据新的容量(原先容量的2倍)创建一个新的数组,
    Entry[] newTable = new Entry[newCapacity];
    boolean oldAltHashing = useAltHashing;
    useAltHashing |= sun.misc.VM.isBooted() &&
            (newCapacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
    boolean rehash = oldAltHashing ^ useAltHashing;
    //调用transfer方法完成旧数组到新数组的转换
    transfer(newTable, rehash);
    table = newTable;
    threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}
如何将旧的数组转换为新的数组,从下面代码可以看出,需要逐个遍历数组元素,也就是每个桶.
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;
            //是否需要重新计算哈希值
            if (rehash) {
                e.hash = null == e.key ? 0 : hash(e.key);
            }
            //为该元素重新计算下标
            int i = indexFor(e.hash, newCapacity);
            //由下面代码可以发现,扩容后,HashMap会将桶对应链表进行了反转
            e.next = newTable[i];
            newTable[i] = e;
            e = next;
        }
    }
}

    扩容的主要实现在transfer方法中 , 需要遍历整个数组的全部桶 , 进而遍历桶的所有链表元素 , 为该元素重新计算下标 , 采用了链表的头插入方式 , 如果在发生了哈希冲突的情况下 , 先放在新数组中Entry链上元素最终会被放在链表的尾部

 

 

 

三  get方法解析

    public V get(Object key) {
        //key为null的话,调用getForNullKey方法
        if (key == null)
            return getForNullKey();
        Entry<K,V> entry = getEntry(key);

        return null == entry ? null : entry.getValue();
    }

    /**
    private V getForNullKey() {
        //对于key为null的情况,直接遍历位于0 下标的Entry链表,如果某个元素的key为null,返回value,找不到就返回null 
        for (Entry<K,V> e = table[0]; e != null; e = e.next) {
            if (e.key == null)
                return e.value;
        }
        return null;
    }

 

    final Entry<K,V> getEntry(Object key) {
        //计算key 的hash值 , 通过hash值计算其所在的Entry链表所在的数组下标位置,定位到其所在的Entry链表后,遍历该链表,依次和每个元素进行对比,如何判断两个key是否是同一个key呢?
        第一: hash值相等
        第二: key相等
        如果遍历不到,说明之前为put过该元素,返回null
        int hash = (key == null) ? 0 : hash(key);
        for (Entry<K,V> e = table[indexFor(hash, table.length)];
             e != null;
             e = e.next) {
            Object k;
            if (e.hash == hash &&
                ((k = e.key) == key || (key != null && key.equals(k))))
                return e;
        }
        return null;
    }

 

JDK1.8

 

  •  成员变量
//默认的容量,默认为16    
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16


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

//默认的加载因子    
static final float DEFAULT_LOAD_FACTOR = 0.75f;

//当链表的长度大于该值的时候,会进行树化,转化为红黑树
static final int TREEIFY_THRESHOLD = 8;

//当红黑树的元素个数小于该值的时候会转化为链表    
static final int UNTREEIFY_THRESHOLD = 6;

   
static final int MIN_TREEIFY_CAPACITY = 64;


  • HashMap如何添加元素

   HashMap添加元素的时候,调用put方法,put方法内部又调用了putVal方法,具体流程如下:

  1 判断HahsMap的数组的是否已经初始化,

  2 根据数组长度和给定的key的hash值计算元素下标,如果该下标处元素为null.说明未添加过元素,就可以直接用key和value构造一个Node,然后赋值给该下标处

  3 如果根据数组长度和给定的key的hash值计算的下标处不为空,说明已经有元素被添加,即发生了哈希冲突。此时需要判断下面几种情况:

   如果下标处元素不为空,那么该下标处的数据结构有两种情况:

  第一种情况:链表

  第二种情况:红黑树

HashMap进行如下几种判断:

判断1:如果位于该下标处的节点的key值和给定的key相等且hash值相等 , 那么获取该处元素的引用

判断2:判断1不满足条件,那么就判断是否是红黑树.如果是红黑树,那么调用操作红黑树的相关方法

判断3:如果判断1和判断2条件都不满足,则则进行链表的遍历操作,如果链表中有key相等的节点,则替换其value,返回旧的value,

如果不存在,则将该元素追加在链表尾部

 public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

    /**
     * Implements Map.put and related methods
     *
     * @param hash hash for key
     * @param key the key
     * @param value the value to put
     * @param onlyIfAbsent if true, don't change existing value
     * @param evict if false, the table is in creation mode.
     * @return previous value, or null if none
     */
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        //如果HashMap内部的数组为空或者其长度为0,说明还未初始化,进行扩容
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        //根据key的hash值和数组长度计算得到下标,如果下标处元素为空,说明还没有元素被添加到该下标处.直接根据给定的key和value构造Node节点,赋值
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        //如果不为空,说明发生了hash冲突
        else {
            Node<K,V> e; K k;
            //如果该下标处的元素的hash值和给定key的hash值相等且key相等
            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);
            //如果下标处的元素的key值和给定的key不一致,且该下标处为链表
            else {
                for (int binCount = 0; ; ++binCount) {
                    //如果该链表中未添加过该key,则将给定的key和value添加到链表尾部
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        //如果大于阈值,将链表转为红黑树,添加结束
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    //如果链表中存在key和hash相等的元素,
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            //设置新的value,返回旧的value
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        //modCount次数+1
        ++modCount;
        //如果元素数量大于阈值,进行扩容操作
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

 

  • HashMap如何扩容
  •  final Node<K,V>[] resize() {
            //获取当前数组的引用
            Node<K,V>[] oldTab = table;
            //获取当前Hashmap的容量,即数组的长度
            int oldCap = (oldTab == null) ? 0 : oldTab.length;
            //获取扩容的阈值
            int oldThr = threshold;
            //定义新的容量和扩容阈值
            int newCap, newThr = 0;
            //如果旧的数组容量大于0,即Hashmap不为空,说明数组已经添加过元素,发生扩容的原因是数组容量达到了扩容阈值,则会进行如下判断:
            
            if (oldCap > 0) {
            //1: 如果数组容量已经大于最大值,则将扩容阈值设置为最大值,且返回旧的容量,也就是不扩容
                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 {               // zero initial threshold signifies using defaults
                newCap = DEFAULT_INITIAL_CAPACITY;
                newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
            }
            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) {
                for (int j = 0; j < oldCap; ++j) {
                    Node<K,V> e;
                    if ((e = oldTab[j]) != null) {
                        //扩容之前,要释放掉之前的Node节点的引用
                        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);
                        //如果是链表
                        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,说明该元素在旧数组中的下标和新数组中下标是一致的,下标都小于旧数组长度
                                if ((e.hash & oldCap) == 0) {
                                    if (loTail == null)
                                        loHead = e;
                                    else
                                        loTail.next = e;
                                    loTail = e;
                                }
                                //如果不满足条件(e.hash & oldCap)==0,说明该处链表的元素下标会发生变化,新的下标 = 旧的下标 + 旧数组容量
    
                                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;
                            }
                        }
                    }
                }
            }
            return newTab;
        }

    从上面可以看出

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值