HashSet 不可重复性 源码学习

HashSet 具有不重复的特性,来专门学习一下。

实体类:StudyDTO

public class StudyDTO {

    public StudyDTO() {
    }

    public StudyDTO(int id, String name) {
        this.id = id;
        this.name = name;
    }

    private int id;

    private String name;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
    
}

测试代码:

    public static void testHashSet(){
        //看对象是否重写了equals和hashCode,未重写的时候,比较的就是this == obj
        HashSet hashSet = new HashSet();
        hashSet.add(new StudyDTO(1,"a"));
        hashSet.add(new StudyDTO(1,"a"));
        hashSet.add(new StudyDTO(1,"a"));
        hashSet.add(new StudyDTO(2,"a"));
        hashSet.add(new StudyDTO(3,"b"));
        System.out.println(hashSet.size());
    }

首先,来看一下new HashSet()部分的源码:

public HashSet() {
    map = new HashMap<>();
}

其它方式的构造仅仅是参数跟随变化,无大的区别。
仅从这一句new HashMap,就知道原因了,HashMap中的key是不允许重复的,而HashSet中存放的对象,就是HashMap中的key。
来看具体的分析:

hashSet.add(new StudyDTO(1,"a"));  ①

public boolean add(E e) { ②
	//private static final Object PRESENT = new Object(); 这就是一个空对象
    return **map.put(e, PRESENT)**==null;
}

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

调用顺序:
测试代码中的①->HashSet中的②->HashMap中的③
而putVal,即为HashMap中也是非常核心的存储方法,换言之,所谓HashSet的put调用,就是HashMap中有K同V的put方法。(HashMap中的putVal方法非常牛X,大家有空了可以去学学)。

再来看一下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;
    //进行哈希判定,看传入对象的hashCode是否与当前哈希桶中存在,不存在,就直接存储,存在,则走else
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    else {
        Node<K,V> e; K k;
        //hashCode和equals相同,就进行替换
        //这里需要重点看一下,直接使用 测试操作 hashSet.add(new StudyDTO(1,"a")) 存储的时候,每次都是一个新的值,不会进行冲突判定
        //但是当重写了hashCode和equals以后,就会发现,用hashSet.add(new StudyDTO(1,"a")) 存储时,不论多少次,都是一个,并进入下面的替换。
        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);
                    //当链表长度到8位时,转成红黑树
                    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调用进行了说明,其它地方同样也会触碰,不过就不再详细说明了。
通过上面的代码可以看到,处理的全是hash方法处理过了的key,value的作用就是被存储,并未对其进行什么具体的操作。
而在HashSet的put方法中,对象均是以key的形势存储进行,value=new Object(),所以它完美的调用了HashMap中的操作。
而putVal中的操作,又 保证了K的不重复性。
所以HashSet中可以存放一个null以及对象不可以重复,就有了解释。

通过以上说明,就可以知道,当执行测试代码时,可以得到结果为:5
因为每个new StudyDTO都是一个新对象,它的hashCode每次都是一个新值,在哈希桶中不重复。

再对StudyDTO进行添加hashCode和equals方法:

@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    StudyDTO studyDTO = (StudyDTO) o;
    return id == studyDTO.id &&
            Objects.equals(name, studyDTO.name);
}

@Override
public int hashCode() {
    return Objects.hash(id, name);
}

这时,可以得到结果为:3
其原因就是上面所说明的,调用putVal方法时的处理,hashCode与equal均相等,
前面的new StudyDTO(1,“a”)会被后面的覆盖,结果自然为3.

新人学习源码,总有一种雾里看花的感觉,不够透彻,如有地方写的不对,请各位大神指出。

猜你喜欢

转载自blog.csdn.net/qq_34987395/article/details/82988896