详述add底层代码

本文详细解析了HashSet中add方法的底层实现,通过分析HashMap的put方法、hashCode和equals方法,揭示了如何通过哈希值和键值比较确保元素不重复。文章通过实例展示了多次添加相同对象的情况,解释了为何HashSet不允许重复添加元素。
摘要由CSDN通过智能技术生成

前面我们分析了equals和contains方法的底层代码,接下来分析add底层代码。

看一段代码。

情况一:

package blog;

import java.util.HashSet;

public class Test {

    public static void main(String[] args) {
    	HashSet<String> set = new HashSet<>();
    	set.add("Tom");
    	set.add("Tom");
        System.out.println(set.size());//输出1,为什么	
    }
}

HashSet<String> set = new HashSet<>();先创建一个HashSet对象,调用了该类中无参构造方法,执行了该构造方法中map = new HashMap<>();其中map为HashSet全局变量

set.add("Tom");调用HashSet 中add方法,如下图。map为全局变量,指向的是创建HashSet对象时创建的HashMap对象

我们前面说过,set中添加信息时,不允许重复,那么add是怎么不允许重复的呢?接下来看它的底层代码。

ctrl键+点击add得到上图代码,也就是HashSet 中add方法,此方法调用了HashMap中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;
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    else {
        Node<K,V> e; K k;
        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) {
                if ((e = p.next) == null) {
                    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;
                p = e;
            }
        }
        if (e != null) { // existing mapping for key
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            afterNodeAccess(e);
            return oldValue;
        }
    }
    ++modCount;
    if (++size > threshold)
        resize();
    afterNodeInsertion(evict);
    return null;
}

底层代码分析

 public V put(K key, V value) {
        return putVal(
hash(key), key, value, false, true);  //put方法调用了putVal方法,putVal方法里又调用了hash方法,代码如下
    }

hash方法返回一个hash值,其中“(判断语句)?表达式1 :表达式2”是一个三目运算符,前面判断为真,则执行表达式1,否则表达式2,key就是添加的那个Tom,因为key==null,不成立,所以执行“:”后面的分支,hashCode也是一个方法,可以认为是返回对象地址的,比如

hashCode源代码

上面调用的的是Object.class中的hashCode方法,源码为

下面调用的是String重写后的hashCode方法,(表面调用Object类中的hashCode,实际上调用String重写后的hashCode方法——多态)

重写后hashCode代码

因为hash初值相同(默认为0),这里的value就是Tom,Tom相同,length就相同,字符数组val[]中各元素对应相等,对应的ASCII值也就相同,所以得到的h值就相同。所以相同内容得到相同结果,不同内容得到不同结果。

注意:name1与name2地址并不相同,只是他们的hashCode相同

总结:不同对象的hashCode可能不相同,在Object类中,不同对象的hashCode一定不同,但在String类中,只要字符串内容相同,则hashCode相同,要看调用的是那个类里的hashCode(注意观察是否存在多态);相同对象的HashCode的值一定相同。所以HashMap中hash方法:传入相同的对象,得到相同的结果。

 final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
//table是一个无参的数组,初始默认值为空null,所以(tab=table)==null为true((tab=table)==null清空tab,使tab为null)
            n = (tab = resize()).length;//resize()也是hashMap中一个方法,可以点进去看一看,其中有一行是table=newTab,如图

  表明table和tab指的是同一个地址——newTab

最后返回一个newTab,所以table与tab都指向newTab,长度默认为16,即n=16,如图
        if ((p = tab[i = (n - 1) & hash]) == null)//15与上面得到的hash值进行一个按位与运算得到一个 i 值(不一定是0或1),将tab[i](tab与table指向同一个地址,所以table[i]也是有值的,且相同)赋给了p,因为最初数组并没有元素,所以是空的,所以p==null为true,执行下面语句
            tab[i] = newNode(hash, key, value, null);//开辟一个新结点newNode,返回key值,即tab[i]中存入了Tom。然后直接到最后,返回了null
        else {
            Node<K,V> e; K k;
            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) {
                    if ((e = p.next) == null) {
                        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;
                    p = e;
                }
            }

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值