HashMap源码解读(下篇)

举报
VIBE 发表于 2022/10/22 10:15:49 2022/10/22
【摘要】 前言上一篇博主对HashMap中的属性和Put方法进行了逐句解读,链接如下:HashMap源码解读(中篇)本篇将解读HashMap的resize()方法,构造方法,以及拓展一些HashMap中的特性。 一、Put方法核心流程1 若HashMap还未初始化,先进行哈希表的初始化操作(默认初始化为16个桶)2.对传入的Key值做hash,得出要存放该元素的桶编号a.若没有发生碰撞,即头节点为空...

前言

上一篇博主对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 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的主要源码解读了,有帮助的话可以点个赞收藏起来慢慢学习~

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。