JAVA学习心得——详述HashSet中的.add();方法

下列代码是向set集合中添加元素。

public class Test {
	public static void main(String[] args) {
		HashSet<String> set = new HashSet<>();
		set.add("Tom");
	}
}

一个简单的.add方法,但是源码有很多值得耐人寻味的东西。
首先:
HashSet<String> set = new HashSet<>();这行代码是用来创建一个名为set的HashSet对象,我们深入源码发现:(源码如下:)
在这里插入图片描述
set调用了该类中无参构造方法,但是执行的是该构造方法中map = new HashMap<>();map为蓝色字体,是HashSet的全局变量。
set.add("Tom");这行代码是向set集合中添加元素。调用HashSet add方法,我们打开源码:
在这里插入图片描述
发现实际调用的是HashMap的添加方法,只不过是value是成为一个全局变量PRESENT。这时map是一个全局变量指向创建HashSet对象时创建的HashMap对象。

另外add方法的返回值类型是boolean,所以如果返回值是null那么表示添加成功。


探究.add方法也即是探究.put方法。
在这里插入图片描述
打开put方法有一个putVal方法,所以我们其实是探究putVal方法。


明白大概.add方法如何分析之后,我们通过源码详细分析以下三种情况:

public class Test {
	public static void main(String[] args) {
		HashSet<String> set = new HashSet<>();
		set.add("Tom");
		set.add("Tom");
		set.add("new String Tom");
	}
}

以下是源码:

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;
    }
  • 第一次添加元素的过程:set.add(“Tom”);

首先定义Node<K,V>[] tab;意思是创建一个名为tab的Node节点集合,然后if ((tab = table) == null || (n = tab.length) == 0)对比tab和table是否为空,因为table是全局变量所以程序运行开始之前初始值null,判断为true,所以不用判断(n = tab.length) == 0,直接执行 n = (tab = resize()).length;,resize方法打开底层并没有看懂,但是知道是返回值是newTab,其实此时tab已经是被替换了成为长度为16的数组,n等于tab的长度最大值是16,接下来判断p = tab[i = (n - 1) & hash]) == null 其中因为i的范围是0~15,所以n-1来控制防止数组溢出,至于&hash是程序自动编译计算得来的tab的地址(在第三种情况我会详细描述)。这样就执行tab[i] = newNode(hash, key, value, null);不执行else,直接执行图中代码(源代码的最后一段)在这里插入图片描述
返回值是null,所以put方法完成返回值null,然后boolean判断是turn,.add方法完成。


  • 情况二:添加了相同元素时,set.add(“Tom”);如何执行?

下面讨论第二种情况:如果添加了相同元素时,add方法是如何进行判断的:?

		HashSet<String> set = new HashSet<>();
		set.add("Tom");
		set.add("Tom");

此时已经在set集合中添加了一个tom元素,要再添加一个tom,这是putVal是这么运行的,首先执行 if ((tab = table) == null || (n = tab.length) == 0)此时的table已经被第一次添加元素时赋值所以不是null,tab.length明显也不为0,所以接下来执行if ((p = tab[i = (n - 1) & hash]) == null)由于此时的添加的元素的tab和第一次添加的tab,元素是一样的所以tab也是一样的,其找寻的地址也

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值