JDK哈希表原码分析

目录

回到Object,万物之母。

hashCode

JKD的源码分析

HashMap源码

查看源码的方法 四种

1. JDK8z之后HashMap的结果如下: 

2.关于HashMap源码中属性的解读​编辑

 3.put方法解读

4.关于哈希桶的数量必须为2^n的说明

关于Set集合和Map集合的关系


JDK哈希表原码分析,这是面试常考问题:非常高频的问题

阅读源码的能力:快速成长的捷径。JDK的源码质量非常之高,先去学习和模仿大神的思路和代码。

关于JDK类集源码的阅读。数据结构的基础+Java底层代码的熟悉程度
 

回到Object,万物之母。
 

hashCode

1.

 2.

 Object提供的hashCode可以将任意对象转为int,不同的对象(地址不同)

 原则上一定转为不同的int。

注释:原则上自定义的类若需要保存到HashMap哈希表中,不能直接使用Object提供hashCode
需要覆写这个方法。

为什么需要覆写?

因为调用Object的返回值太大了,基本上都是千万,亿级别的。数组要开辟的空间太大了。

 

 举例:自定义一个Student类

 hashCode() :用来计算哈希值(索引值)

 equals( ): 用来判断哈希表是是否存在了相同的key值

所以这两个方法都需要覆写。

问题:

 第一个:必须相同。 第二个:不一定相同

结论:

1. equals相同的两个对象,就认为是同一个对象。哈希表中这个对象有且只能有一个。经过哈希函数运算后,这俩对象保存的索引也应该相同心~

2. hashCode相同,说明此时发生了哈希冲突,不—定就是相同的对象到底是否相同还要取决于equals方法。

3. 在哈希表中,只有equals和hashCode都相同的对象,才是 唯一对象
 

注释:Object中的equals默认比较的是地址,如果你想比较别的内容,比如说是key值,覆写就可以了。 所以上面说了,equals相同,按默认的equals相当于是地址相同,那肯定是同一个对象了,都同一个对象了,哈希值肯定也是相同。至于计算出相同的哈希值,说明很肯定是发生了哈希冲突,并不一定就是同一个对象。

举例: 把这三个test保存到HashMap中的key中,会保存几个? 

2个:test1和test2。test3和test1根据就是同一个对象,只不过是在栈中又开辟了块空间叫test3的来保存了第一个对象的地址。

证明:put了三个对象,打印结果会怎么样?

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

public class Test1 {
    public static void main(String[] args) {
//        //创建两个对象
//        Test1 test1 = new Test1();
//        Test1 test2 = new Test1();
//        //调用hashCode
//        System.out.println(test1.hashCode());
//        System.out.println(test2.hashCode());
//        //一个相同引用对象
//        Test1 test3 = test1;//都指向test1对象地址
//        System.out.println(test3.hashCode());

        Student stu1 = new Student("张三", 18);
        Student stu2 = new Student("李四", 18);
        Student stu3 = new Student("张三", 18);
        Map<Student, String> map = new HashMap<>();
        map.put(stu1,"111");
        map.put(stu2,"222");
        map.put(stu3,"333");
        System.out.println(map);
    }
}

class Student{
    private String name;
    private int age;

    @Override
    public int hashCode() {
        return 0;
    }

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

    @Override
    public boolean equals(Object o) {
       if(this == o){
           //说明此事地址相同,是同一个对象
           return true;
       }
       //如果此时的 o 是个学生类披着Object的外衣
       if(o instanceof Student){
           //需要强制类型转换
           Student stu = (Student) o;
           return this.age == stu.age && this.name.equals(stu.name);
       }
       return false;
    }


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

2个对象 :stu1和stu3这两个对象的equals相同,此时在HashMap只会存储一份儿。相同的一个对象。equals相同的对象,就认为是同一个对象。
 

如果此时把equals方法里面判断直接都注释,无论什么对象比较,都是不同的,false。


再次运行:

 现在就存储了三个对象。

我们就是要通过equals方法告诉JDK到底什么样的才算是同一个对象。

自定义对象作为key的唯—性就是通过equals方法保证的。

---------------------------------------------------------------------------------------------------------------------------------

JKD的源码分析

HashMap源码

注释:看源码不像是看书,一定要有目的的去看,带着疑问去看。

比如:

1. HashMap的哈希函数是如何设计的

2. put方法的逻辑,到底是如何存储元素的;

3. 当发生冲突时如何解决的;

4. 哈希表冲突较严重,如何扩容resize;

注释:

查看源码的方法 四种

1. 在IDEA界面双击 shift 键,会弹出一个界面,搜索就行。

⒉直接在使用的类上,ctrl +鼠标左键点进去即可 : 接口方法


 

3. Ctrl + Alt + 鼠标左键 :选择实现子类的方法,而不是接口方法

 

4. ctrl +  <  (方向盘左键) 和 ctrl + > (方向盘右键)

可以左右切换你访问的方法顺序,有点像入栈模式。

注释:有些是  ctrl + Alt +  <  (方向盘左键) 和 ctrl +  Alt +   +  > (方向盘右键)

注释:有些软件的热键可能会冲突到底按了没反应。

比如看看HashMap的构造方法,是怎么初始化的。

1. JDK8z之后HashMap的结果如下: 

 数组+链表+红黑树(冲突严重的链表会被"树化",将链表转为红黑树,提高冲突严重的链表的查询效率)

 注释:JDK8之前,JDK7以及更老版本,HashMap就是 数组+链表。红黑树是JDK8才加入的。

(这个问题面试大概率会问)

注释:IDEA左下角 Structure,点一下这个,类的所有方法就都显示出来了。

2.关于HashMap源码中属性的解读

 一个个来解析:

1.  DEFAULT_INITIAL_CAPACITY 也叫 哈希桶  

注释:哈希桶就是数组的大小

2.  最大数组容量

 3. 负载因子

注释:超过计算值 12 就需要扩容

 4.  树化阈值

5.  解树化

 6. 

若某个链表长度 >=8 ,而此时哈希桶的数量不足64则只是简单的哈希表扩容而已。
 

 3.put方法解读

hash()方法:首先先计算一下key的哈希值

再点进行看看。

哈希Map的哈希方法 :hash

 空对象放在编号为0的桶中。

不为空:key对象所在类的hashCode方法
若没有覆写,默认调用Object提供的hashCode方法

你会发现上面的hash结果还是很大,1111 的。 

注释: 这个值还不是当前数组的索引,只是经过hash运算得到的一个比较均衡的

 

结论:高低16位都参与哈希函数的运算,尽可能保证不同key映射到比较均衡的状态

看数组索引的最终取值:还是看putVal()方法

 

这个求出来的值才是索引

 当 n 是 2的 n次方 的时候,用n来 取模,或者是 与运算 结果都是一样的。

 

提问:既然 % 取模运算 和  & 位运算 在这种情况下是一样的,那为什么要 & hash 而不是 %运算

答:位运算的效率是最高的,这是把性能发挥到极致。所以能做 & 位运算的在HashMap中大量的进行着都是 &位运算。
 

有了上面两步操作,就可以将任意的正整数转为哈希桶数量之内的小整数  ==》 i就是当前key求的哈希桶的编号

可以用Debug进行查看,需要强制进入。Alt  + Shift F7

 

 putVal方法分析:

 1.

2.  

 3.

4. 5.

6. 树化处理方法:

 

 7.

总结:put方法

 注释:看源码一定要先有大题思路,细节是如果实现的先不用管。

如果想计算自定义类型的哈希值,就可以调用Objects类的hashcode方法。

牵扯到自定义类型的hashCode的覆写,可以调用Objects类的hashCode方法

传入你想要计算的属性,当自定义类的两个对象里面的属性值相同,得到的哈希值就一定相同。

 注释:objects是Object类的工具类

就看两个方法 hashCode 和 equals

标准: 

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

 因为我们覆写的equals方法里面也有比较成员属性。

 所以才说equals相同,haxhCode一定相同。

---------------------------------------------------------------------------------------------------------------------------------

4.关于哈希桶的数量必须为2^n的说明

 (n - 1)&hash == hash % n

先看看:HashMap的构造方法


 

我们在new这个HashMap的时候,并没有产生哈希桶,或者说数组还没设置长度。 

 

内部数组还没有初始化,只有第一次调用put方法时才初始化内部哈希桶数组。(懒加载模式)

第一次使用(添加)时才初始化相应的内存
 

还有有参构造方法

当你传入的数字表示2^n 大小值,就自动帮你转换。 最接近的。

 当传入31时,会自动转换成32。这样才不会让哈系方法失效。

 

5.

关于Set集合和Map集合的关系

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

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


HashSet其实就使用HashMap保存的

TreeSet其实就使用TreeMap保存的
 

 

注释:value值用个空值来代替

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

因此Set保存的元素不可重复!!
 

HashSet能否保存null :   可以因为HashMap的key可以为null

TreeSet能否保存null  :   不可以因为TreeMap的key不能为null

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值