JDK1.8深入HashMap详解

本文深入探讨了JDK1.8中HashMap的内部实现,包括常量、变量定义,以及put方法的源码分析。详细解释了扩容机制、树化阈值和退化阈值,展示了HashMap如何处理冲突,以及在数据结构转换(链表到红黑树)的过程。此外,还讨论了HashMap的快速失败机制和迭代器的工作原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

HashMap

 

常量、变量

//初始Node<K,V>[] 大小为16

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;

 

//Node<K,V>[] 最大值为2的30次方

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;

 

//HashMap的基本单位(后续我们会说成一个个桶)

transient Node<K,V>[] table;

 

 

transient Set<Map.Entry<K,V>> entrySet;

 

//HashMap以存储数据的数量

transient int size;

 

//记录hashmap的修改次数,应用于fail-fast机制

transient int modCount;

 

//扩容阈值

int threshold;

 

//加载因子

final float loadFactor;


 

Node内部类其实现了Entry接口

Node重写了hashCode与equals方法,具体自己看源码

 

4个构造方法:

    //如果入参initialCapacity大于MAXIMUM_CAPACITY(2的30次方)则让其值为2的30次方

    如果入参initialCapacity与loadFactory入参不正确则抛出异常

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

    }

 2、public HashMap(int initialCapacity) {

        this(initialCapacity, DEFAULT_LOAD_FACTOR);

    }

 3、public HashMap() {

        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted

    }

 //牵扯太多 后续再说

 4、public HashMap(Map<? extends K, ? extends V> m) {

        this.loadFactor = DEFAULT_LOAD_FACTOR;

        putMapEntries(m, false);

    }

 

 

  

 


 

请看下面一段代码

    // 声明了一个Hash集合叫map,此时我们只是给map的加载因子赋予了默认值。   

     Hash<String, Object> map = new HashMap<>();

   //我们put一个String类型的对象进入map

    map.put("1","test1");

请看put源码:

       

     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;//tab用来接当前map的table数组,p是table[]对应位置的元素

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

            n = (tab = resize()).length;//如果map为空此时做出扩容操作

        if ((p = tab[i = (n - 1) & hash]) == null)//(n-1)&hash计算出当前hash值对应table[]数组中哪个位置,如果当前位置为空则直接放在这里

            tab[i] = newNode(hash, key, value, null);

        else { //如果当前位置不为空

            Node<K,V> e; K k;//k表示当前节点的key值,e用来记录旧节点(覆盖情况的出现)

           //每次判断桶中的第一个元素即table[i],得到覆盖或者保持旧值取决于onlyIfAbsent

            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) { //遍历桶中的链表,e用来记录p节点的next

                    if ((e = p.next) == null) { //只要遍历到p的下一个节点为空就直接连接到p的下一个节点

                        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;//如果遍历到key相同的节点就跳出循环

                    p = e;//往链表下一个节点移动

                }

            }

            if (e != null) { // existing mapping for key//put进的key值与链表上某一节点的key值相同

                V oldValue = e.value;

                if (!onlyIfAbsent || oldValue == null)

                    e.value = value;//赋予新值

                afterNodeAccess(e);

                return oldValue//返回旧值

            }

        }

        ++modCount;//应用于fast-

        if (++size > threshold)

            resize();

        afterNodeInsertion(evict);

        return null;

    }


依次解释putVal中hash()、resize()、putTreeVal()

hash()

    static final int hash(Object key) {

        int h;

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

    }

    返回一个int型基本数据。

    1、如果key是null那么返回0,说明HashMap是可以把null作为Key传入的,

    2、如果key不是null那么返回的是,key的hash值的前16位异或后16位,因为一个int是4个字节(32位二进制)。

    充分利用了其高位数据,使hash值更加散乱。

 

来看resize()--->HashMap的扩容机制

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

        Node<K,V>[] oldTab = table;//记录旧数组,待会需要把旧数组的值装入新数组中

        int oldCap = (oldTab == null) ? 0 : oldTab.length;//oldCap记录原数组大小

        int oldThr = threshold;//oldThr记录原数组的扩容阈值

        int newCap, newThr = 0;//newCap记录新的数组大小、newThr记录新的数组的扩容阈值

        if (oldCap > 0) {    //原数组不为空

            if (oldCap >= MAXIMUM_CAPACITY) { //原数组大小大于MAXIMUM_CAPACITY,则把threshold赋值为2^31-1

                threshold = Integer.MAX_VALUE;

                return oldTab;

            }

            //newCap = oldCap*2 之后如果小于MAXIMUM_CAPACITY且oldCap大于16(说明构造或经过了扩容),那么新阈值*2

            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&

                     oldCap >= DEFAULT_INITIAL_CAPACITY)

                newThr = oldThr << 1; 

        }

        else if (oldThr > 0) 

            newCap = oldThr;

        else {               //初始化hashmap

            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];//新建了一个大小为newCap的Node数组

        table = newTab;

        if (oldTab != null) { //既然我们已经有了新的Node[](空的)、新的阈值,那么我们只差copy新数组的值了

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

                Node<K,V> e;//用来暂时存储原数组中的元素

                if ((e = oldTab[j]) != null) { //先把非空旧节点赋值给e,然后把旧节点赋予空值(准备gc)

                    oldTab[j] = null;        

                    if (e.next == null)//如果当前桶只有一个元素(而非链表结构)

                        newTab[e.hash & (newCap - 1)] = e;//找出e节点在新table[]中的位置,并赋值

                    else if (e instanceof TreeNode)//如果e节点是树节点(说明桶中是红黑树了),须知!只有树化时才会把Node转换为ThreeNode

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

                    else { //说明是链表结构

                        Node<K,V> loHead = null, loTail = null;//低位链头链尾

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

                        Node<K,V> next;

                        do {

                           

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值