HashSet中的add方法(对集合底层的分析)

本文深入探讨HashSet的add方法,通过源码分析不同数据类型的哈希处理,包括引用类型、基本数据类型包装类和自定义类型。讲解了HashSet如何在HashMap中存储元素,以及值不允许重复的原因,揭示了哈希冲突时的存储策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

HashSet中的add方法

源代码如下:

public boolean add(E e) {
      return map.put(e, PRESENT)==null;
}
由源代码(HashSet类中add方法)可以看出PRESENT是常量类型(由fina修饰,全部大写),map是HashMap类型的:
private transient HashMap<E,Object> map;

private static final Object PRESENT = new Object();

public HashSet() {
     map = new HashMap<>();
}
​​ public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }​​

执行代码:

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

执行第一行代码时,调用了无参构造方法---->上面的HashSet()HashMap()

执行第二行代码时,调用add方法存储数据。证明了HashSet存储的数据实质存储的是HashMap中key的值。HashSet存储的值不允许重复实际上是因为HashMap中key的值不允许重复。

hash方法中不同数据类型的参数的区别:

hashSet中add方法源码:

​public boolean add(E e) {
      return map.put(e, PRESENT)==null;
}
​

 hashMap中put方法源码:

public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

 hashMap中hash方法

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

一层层剖析,可知:添加的Tom分别传到了参数 E e、K key、hash(key)中。

引用类型

将String类型的值传到参数中,代码如下:

import java.util.HashSet;

public class Test {

        static int hash(Object key) {
		int h;
		return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
	}

	public static void main(String[] args) {

		HashSet<String> set = new HashSet<String>();
		// hashSet存储的数据实质上存储了hashMap的key
		set.add("Tom");

		int hash = hash("Tom");
		System.out.println(hash);
		hash = hash(new String("Tom"));
		System.out.println(hash);
	}
}

运行结果:84275
                  84275 

结果说明:字符串类型的值传给参数(Object key)中,实际上是一个上转型是对象。h = key.hashCode()) 实际上运行的是String类中的hashCode,源代码如下:

public int hashCode() {
        int h = hash;
        if (h == 0 && value.length > 0) {
            char val[] = value;

            for (int i = 0; i < value.length; i++) {
                h = 31 * h + val[i];
            }
            hash = h;
        }
        return h;
    }

由代码可知:字符串的内容将其变成字符数组,然后遍历将其换成数值。String类型如果内容相同,则返回值相同

基本数据类型包装类

将Integer类型的值传到参数中,代码如下:

hash = hash(10000);
System.out.println(hash);
hash = hash(new Integer(10000));
System.out.println(hash);

运行结果:10000
                  10000 

结果说明:字符串类型的值传给参数(Object key)中,实际上是一个上转型是对象。h = key.hashCode()) 实际上运行的是Integer类中的hashCode,源代码如下:

@Override
public int hashCode() {
      return Integer.hashCode(value);
}

代码说明:该无参hashCode()方法调用了静态有参方法hashCode(value):

public static int hashCode(int value) {
        return value;
    }

由代码可知:基本数据类型包装类,如果值相同,则返回值相同

自定义类型

hash = hash(new Student());
System.out.println(hash);
hash = hash(new Student());
System.out.println(hash);

运行结果:5433712
                  2430314 

结果说明:因为新建的学生类中没有hashCode()方法, 因此会调用继承的Object类中的hashCode()方法:---------->public native int hashCode();--------->native:原生的,调用的c语言 中的方法。自定义类型如果地址不同,则返回结果不同

hashSet中值是如何存储的(底层源码)

hashSet中add方法源码:

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

 hashMap中put方法源码:

public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

  hashMap中putVal方法源码:

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;
    }

 执行下列代码:

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

1.参数列表中hash值是hash(key)方法中key调用的hashCode。

2.为什么成员变量table为null?因为当HashMap对象创建后,默认值是null(transient Node<K,V>[] table);,且在之后的的所有构造方法中未给table赋值,因此table为null;此前创建的全局变量table未赋值,因此为null( transient Node<K,V>[] table;),因此(tab = table) == null 成立,继续执行下一条语句;

3.值不允许重复:

(1)执行第一个set.add("Tom");添加第一个数据时,此时hash即为"Tom","i = (n - 1) & hash"表示i为n-1按位与hash的随机的一个角标,由于tab中所有元素都为null,不可能和hash相同,因此(p = tab[i = (n - 1) & hash]) == null成立,继续执行下一条语句; 将数据存入tab[i]中,且由于tab和table指向一个地址,因此数据也存入到了table中;

(2)执行第二个set.add("Tom");添加第二个数据时,执行语句:if ((tab = table) == null || (n = tab.length) == 0),由于上一步执行过程中tab数组中存入了第一个"Tom"数据,则(tab = table)不为null,且(n = tab.length)必然不为零,所以if条件不成立,直接执行下一个if语句:------>if ((p = tab[i = (n - 1) & hash]) == null),该条件中hash为第一个"Tom"存储的位置这个"i"与上一步执行过程的"i"相同,即tab[i]已经有了"Tom"数据,因此p= tab[i = (n - 1) & hash])不为null,执行else语句;------>if (p.hash == hash && ((k = p.) == key || (key != null && key.equals(k)))),由于p.hash=tab[i]存储"Tom"数据,因此p.hash=hash;又因为p.key为String类型"Tom",与key相同,因此if条件成立,执行下一条语句;------>然后返回原值,即"Tom"数据。因此添加第二个数据失败


(3)执行第二个set.add("Sun");添加第二个数据时,执行语句:if ((tab = table) == null || (n = tab.length) == 0),由于此前执行过程中tab数组中存入了第一个"Tom"数据,则(tab = table)不为null,且(n = tab.length)必然不为零,所以if条件不成立,直接执行下一个if语句-------->if ((p = tab[i = (n - 1) & hash]) == null),该hash中存储的int类型"Lucy"随机生成了在n-1中的一个i值,且由于该hash存储的数据与此前存储"Tom"数据的hash不同,因此该i与此前的i不同,该tab[i]为null,因此p = tab[i = (n - 1) & hash]为null,执行下一个语句;-------> tab[i] = newNode(hash, key, value, null); 存入"Lucy"数据;
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值