浅析Java底层代码——Java中HashSet类add方法

目录

一、引入

二、底层代码逻辑分析

(1)HashSet.add()方法

(2)HashMap.put()方法

(3)HashMap.hash()方法

(4)Object.hashCode方法

(5)HashMap.putVal()方法

第一次加元素:

标记1:

标记2(HashMap.resize()方法):

第二次加元素(不重复):

第二次加元素(重复):

三、特例(String类)

第一次加入元素:

第二次加入元素(直接赋值创建的String类对象 且相同字符):

第二次加入元素(用new关键字创建String类对象 且相同字符):


一、引入

HashSet中add方法不像ArrayList中的contains方法那么简单,是一个复杂的方法体系,因此在直接研究之前先声明在其中调用的方法(本篇只讲解部分部分调用的方法:

HashSet.add()方法

HashMap.put()方法

HashMap.putVal()方法

HashMap.hash()方法

Object.hashCode方法

String.hashCode()方法

HashMap.resize()方法

二、底层代码逻辑分析

(1)HashSet.add()方法

    public boolean add(E e) {    //HashSet.add()方法
        return map.put(e, PRESENT)==null;
    }

首先我们看 HashSet.add()方法,在我之前的博客Java中的HashSet的常用方法总结中提到过,这里的map是一个全局变量,而利用HashSet无参构造方法时实际上是创建了一个HashMap对象赋给了该全局变量map,所以这里map前省略“ this. ”,因为this指代调用该方法的HashSet对象(this用法详见我的博客详述Java中的局部变量与全局变量),是调用该方法的HashSet对象在调用map全局变量。那么因为全局变量map中存的为一个HashMap对象,所以这里实际上调用了HashMap类的HashMap.put()方法,那么问题来了,HashMap类集合对象添加元素应该是两种泛型,这里为什么只传一个却也能调用HashMap.put()方法?不难发现常量PRESENT,在HashSet底层代码中找到它:

它是一个定义在HashSet类中的静态常量,那我们就了解了,HashSet类中定义的静态常量自动填补了HashMap类集合对象中value参数的空缺(实际上这里“ PRESENT ”前也省略“this.”,同样代指调用该方法的HashSet对象在调用该全局静态常量(属性)) 。接着我们退回来,发现HashSet.add方法的返回值为布尔型表达式,那这个表达式判断的是HashMap.put()方法的返回值是否为空,为空返回true,不为空返回false,但因为该返回结果决定了添加是否成功,所以到这里我们可以暂时性的得出一个结论:若HashMap.put()方法返回值为空,则添加成功。那么是什么决定着HashMap.put()方法的返回值呢?我们来看HashMap.put()方法的底层代码:

(2)HashMap.put()方法

    public V put(K key, V value) {    //HashMap.put()方法
        return putVal(hash(key), key, value, false, true);
    }

这里问题来了,HashMap.put()方法的代码更简单,直接能看出来HashMap.putVal()方法的返回值决定了HashMap.put()方法的返回值是否为空,那这里又得到一个暂时性的结论:若HashMap.putVal()方法返回值为空,则添加成功。这样就要分析HashMap.putVal()方法的底层代码了,不过我们不妨先看看该方法中传入的参数,毕竟这些参数直接决定了该方法返回结果。这里看到,后三个参数都都是直接传入,而且后两个为布尔型(这两个布尔型下文再讲),只有第一个参数是某个方法的返回值,且第二个参数决定了该方法的返回值,这是个什么方法呢?,不妨查看它的底层代码:

(3)HashMap.hash()方法

    static final int hash(Object key) {    //HashMap.putVal()方法
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

这是HashMap.hash()方法,一看,这个方法的返回结果中不仅用到了三位运算符,位移运算符还调用了一个hashCode()方法,不管怎样,我们知道:经过一系列复杂运算,HashMap.hash()方法返回的一定是一个数值。我们再来看hashCode的底层代码:

(4)Object.hashCode方法

    public native int hashCode();    //Object.hashCode方法

 一看,首先,该方法是定义在Object类的,再看,这个方法没有方法体!为什么?没有方法体不应该是抽象方法吗?为什么不加abstract修饰,如果是抽象方法,那么Object类不就是抽象类了吗?但Object不是抽象类啊。实际上这个Object.hashCode()方法并不是用Java语言编写的方法,而是用C语言编写的方法,位于Java的最底层,主要是依据变量在内存中分配的地址计算出一个数。也就是说对于Object.hashCode()方法,地址不同,则返回值不同。从而说明地址不同,hash()方法返回值不同记住这条结论!

那么一旦解决了前面的问题,我们就正式进入核心代码——HashMap.putVal()方法的底层代码:

(由于下面代码较多,所以部分分析我直接写在注释里)

(5)HashMap.putVal()方法

第一次加元素:

    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {    //HashMap.putVal()方法
        Node<K,V>[] tab; Node<K,V> p; int n, i;//这一行首先定义了四个局部变量,两个个数组(tab和p),两个数(n和i)
        if ((tab = table) == null || (n = tab.length) == 0)    //标记1
            n = (tab = resize()).length;    //这里将resize()方法的返回值(实际上是跟tab同类的数组)赋给了tab然后将该“返回值”的长度赋给n  这里对resize()方法做 标记2 经过这行代码则tab与table指向同一块内存区,修改tab,则table也被修改
        if ((p = tab[i = (n - 1) & hash]) == null)    //前面提到过hash局部变量中传入的为hash()方法的返回值,是一个数,然后这个数1与(n-1)进行按位与运算
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值