HashMap源码解读(下篇)

简介: HashMap源码解读(下篇)

前言

上一篇博主对HashMap中的属性和Put方法进行了逐句解读,链接如下:

HashMap源码解读(中篇)

本篇将解读HashMap的resize()方法,构造方法,以及拓展一些HashMap中的特性。


一、Put方法核心流程

1 若HashMap还未初始化,先进行哈希表的初始化操作(默认初始化为16个桶)

2.对传入的Key值做hash,得出要存放该元素的桶编号

  • a.若没有发生碰撞,即头节点为空,将该节点直接存放到桶中作为头节点
  • b.若发生碰撞

(1) 此时桶中的链表已经树化,将节点构造为树节点后加入红黑树
(2) 链表还未树化,将节点作为链表的最后一个节点入链表

3.若哈希表中存在key值相同的元素,替换为最新的value值

4.若桶满了(++size是否大于threshold),调用resize()扩容哈希表。(threshold = 容量*负载因子)

二、自定义类型当作Key传入

现自定义一个Student类:

class Student {
    private String name;
    private int age;
}

若自定义的类型需要保存到Map的Key中,则需要同时覆写hashCode和equals,equals相同的俩对象,hashCode必须相同,反之不一定。

覆写后,Student类如下:

class Student {
    private String name;
    private int age;

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj instanceof Student) {
            Student stu = (Student) obj;
            return this.age == stu.age && this.name.equals(stu.name);
        }
        return false;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

注意:代码中返回的是Object的工具类Objects,传入的属性值相同返回相同的hash值。

在这里插入图片描述

三、HashMap的构造方法

3.1 无参构造器如下:

在这里插入图片描述

当new一个HashMap的时候,若没有指定HashMap大小,默认调用无参构造器,可是此时内部数组还没有初始化,代码中只是简单的把负载因子初始化了,只有当第一次调用put方法时才初始化内部哈希桶数组(懒加载模式)。 第一次使用(添加)时才初始化相应的内存。

3.2 有参构造器如下:

在这里插入图片描述

如图红色框框,代码检查传入的参数是否2的n次幂,若不是调整为最接近 $2^n$ 的数。

在这里插入图片描述

例如:传入31,实际上初始化后threshold为32。

三、resize方法

源码如下:

final Node<K,V>[] resize() {
        Node<K,V>[] oldTab = table;
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        int oldThr = threshold;
        int newCap, newThr = 0;
        if (oldCap > 0) {
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            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) {
                    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;
                            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);
                        if (loTail != null) {
                            loTail.next = null;
                            newTab[j] = loHead;
                        }
                        if (hiTail != null) {
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        return newTab;
    }

源码解读如下

1.若oldTab 为 null ,则证明还没有初始化。

在这里插入图片描述2. 当旧数组不为空的时候,新数组拓展为原数组的一倍。(扩容操作)

在这里插入图片描述
3.初始化操作在这里插入图片描述

4.元素搬移操作,把链表上的元素头插到扩容后的哈希桶中。

在这里插入图片描述

四、Set集合和Map集合的关系(拓展)

先下结论:

Set集合的子类实际上在存储元素时就是放在了Map集合的Key中:

在这里插入图片描述

这也是为啥Set是不可重复的!!!

  • HashSet其实就是使用HashMap保存的
  • TreeSet其实就是使用TreeMap保存的

Set就是用的Map的子类来存储元素,Set的不可重复就是因为元素保存在了Map的Key上。

在这里插入图片描述

  • HashSet 可以保存null ,因为HashMap的key可以为null
  • TreeSet 不可以保存null ,因为TreeMap的key 不能为null。

总结

以上三篇就是HashMap的主要源码解读了,有帮助的话可以点个赞收藏起来慢慢学习~

相关文章
|
1月前
|
算法 Java 容器
Java Review - HashMap & HashSet 源码解读
Java Review - HashMap & HashSet 源码解读
36 0
Java Review - HashMap & HashSet 源码解读
|
1月前
|
Java
从零开始学习 Java:简单易懂的入门指南之HashMap及TreeMap源码解读(二十四)
从零开始学习 Java:简单易懂的入门指南之HashMap及TreeMap源码解读(二十四)
|
6月前
|
安全 Java 应用服务中间件
史上最全的Java容器集合之HashMap(源码解读)(二)
史上最全的Java容器集合之HashMap(源码解读)(二)
43 0
史上最全的Java容器集合之HashMap(源码解读)(二)
|
6月前
|
存储 Java 索引
史上最全的Java容器集合之HashMap(源码解读)(一)
史上最全的Java容器集合之HashMap(源码解读)(一)
46 0
|
存储 Java 索引
“速通“ 老生常谈的HashMap [实现原理&&源码解读]
HashMap在现在已然成为了一个老生常谈的话题, 不管是正在学java的小白还是不断跳槽升值的老鸟, 在面试中HashMap几乎不可避免的会被问到, 你可以不被问到, 但你不能不会, 本篇文章的内容就是HashMap的实现原理和源码解读, 各位观众老爷们一起来看看吧
111 1
|
存储 缓存 Java
2022 最新 JDK 17 HashMap 源码解读 (一)
2022 最新 JDK 17 HashMap 源码解读 (一)
309 0
|
机器学习/深度学习 存储 算法
HashMap底层原理及jdk1.8源码解读【吐血整理1.3w字长文】
HashMap底层原理及jdk1.8源码解读【吐血整理1.3w字长文】
HashMap底层原理及jdk1.8源码解读【吐血整理1.3w字长文】
|
存储 Java 索引
HashMap源码解读(中篇)
HashMap源码解读(中篇)
85 0
HashMap源码解读(中篇)
|
存储 Java 对象存储
HashMap源码解读(上篇)
HashMap源码解读(上篇)
98 0
HashMap源码解读(上篇)
|
存储 机器学习/深度学习 Java
HashMap源码解读(面试题剖析)
HashMap源码深度剖析,对几个有意思的方法进行了分析,如初始化容量如果转换为2的n次幂,扩容过程,存储和获取对象方法,以及面试题的总结
103 0
HashMap源码解读(面试题剖析)