HashSet详解

简介

        Set集合,继承自Collection集合,其特点为:插入无序,存储的元素不能重复,且不可根据index来访问,可以存储null

        Set集合的常用实现类有:HashSetTreeSet

HashSet

        HashSet,内部无序,封装了HashMap,其实也就是用HashMap的key来存储值。

private transient HashMap<E,Object> map;

        HashSet是如何进行去重操作的呢?我们来看一看其的add()方法或许能够找到答案

// 可以看到,HashSet中的add(),其实是调用了HashMap中的put()
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;
}

        putVal()具体是以什么逻辑实现的我在这里不再赘述,有兴趣的小伙伴可以看看我之前写的关于HashMap的文章,这里我们只要关注,putVal()在什么时候给我们返回值了。通过观察,我们发现,当key已经存在时,putVal()会给我们返回该key对应的旧值。还有一种情况就是,如果key不存在,则会为我们返回null。但是,由于HashSet在add的时候,已经内部将我们的value写死了:

return map.put(e, PRESENT)==null;

        所以,如果一个key存在,必然返回的是PRESENT。

        而HashSet中的add()方法就是通过这种方式为防止我们添加重复元素的。当这个key不存在集合中时,底层putVal返回null,然后返回上层调用的map.put(e,PRESENT)== null ,判断为true,于是添加成功,反之添加失败。

        注意!!当我们像HashSet中添加自定义类型时,必须要让自定义类型重写hashCode()和equals()方法。

class People{
    private int age;
    private String name;

    public People(){

    }

    public People(int age,String name){
        this.age = age;
        this.name = name;
    }
}

public class SetTest {
    public static void main(String[] args) {
        People p1 = new People(10,"张三");
        People p2 = new People(10,"张三");
        Set<People> set = new HashSet<>();
        set.add(p1);
        set.add(p2);

        System.out.println(set.size()); // 在没有重写equals() 和 hashCode()的前提下,输出 2。这是为什么呢?
    }
}

 if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))

        这是HashMap判断key是否存在时调用的,我们看到,最终我们都会调用key.equals(),即调用key所在类的equals()方法,如果我们没有重写equals(),则会调用Object类中的equals(),而我们知道,Object类中的equals()方法内部实现是由==来实现的,而==在比较引用数据类型时,比较的是地址值,这样,即使我们new 出来两个相同的People对象,即p1、p2.但是两个对象的存储地址是不同的,那么key != null && key.equals(k) 就会返回false,从而导致整个if语句中得条件判断返回false。于是就会将相同的key放入set集合中。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值