HashSet其实就那么一回事儿之源码浅析

上篇文章《HashMap其实就那么一回事儿之源码浅析》介绍了hashMap,  本次将带你们看看HashSet, HashSet其实就是基于HashMap实现, 所以,熟悉了HashMap, 再来看HashSet的源码,会以为极其简单。下面仍是直接看源码吧:html

public class HashSet<E>
    extends AbstractSet<E>
    implements Set<E>, Cloneable, java.io.Serializable
{
    static final long serialVersionUID = -5024744406713321676L;

    //HashMap ? 没错,HashSet就是经过HashMap保存数据, HashSet的值就是HashMap的key
    private transient HashMap<E,Object> map;
    
    //HashMap 为<key, value>的键值对, 既然HashSet的值就是HashMap的key, 那么HashMap的值呢,固然就是这个PRESENT啦
    private static final Object PRESENT = new Object();
    
    //下面这一系列的构造方法都是建立HashMap, 以前已经介绍过HashMap, 这儿就再也不详说了
    public HashSet() {
        map = new HashMap<>();
    }

    //将一个已知的collection转换为HashSet
    public HashSet(Collection<? extends E> c) {
        //这儿的HashMap的参数为何这么写?
        //上次介绍HashMap的时候可知,若是没有指定HashMap的capacity, 那么默认的就是16
        //根据 threshold = capacity * loadFactor, 能够计算出 capacity
        //Math.max((int) (c.size()/.75f) + 1, 16) 这个意思就是capacity若是没超过16, 那么就直接使用默认的16
        map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
        //将已知的collection转换为HashSet的方法
        //addAll方法是HashSet的父类AbstractCollection的方法,为了便于阅读,会将代码粘贴在下面
        addAll(c);
    }

    public HashSet(int initialCapacity, float loadFactor) {
        map = new HashMap<>(initialCapacity, loadFactor);
    }


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


    HashSet(int initialCapacity, float loadFactor, boolean dummy) {
        map = new LinkedHashMap<>(initialCapacity, loadFactor);
    }
    
    //addAll方法是HashSet的父类AbstractCollection的方法
    public boolean addAll(Collection<? extends E> c) {
        boolean modified = false;
        for (E e : c)
            //此处的add方法由HashSet重写实现
            if (add(e))
                modified = true;
        return modified;
    }
    
    //HashSet的核心方法来了, 没错,就这么简单
    public boolean add(E e) {
        //应证了上面所说的key为HashSet的值
        return map.put(e, PRESENT)==null;
    }
    
    //剩下这些方法都是跟Map相关的了,只要熟悉了HashMap, 那就太简单了,就不说了
    public boolean remove(Object o) {
        return map.remove(o)==PRESENT;
    }

    public void clear() {
        map.clear();
    }
    
}

就这样,HashSet的源码如此简单。下面仍是对HashSet的源码做一个总结吧:java

1. HashSet基于HashMap实现, 以HashSet的值做为HashMap的一个key, 以一个Object对象常量做为HashMap的值。ide

2. 根据HashMap的特性,能够推敲出:HashSet容许拥有1个为null的值, HashSet的值不可重复。测试

3. 在建立HashSet的时候,若是合适,最好指定其内部HashMap的 capacity和loadFactory的值, 至于缘由,在介绍HashMap的时候,提到过。this

 

OK, 讲完HashSet以后,我以为是时候提一下这个问题了: 可能在你们初学java的时候,老师或者书上都推荐你们在重写对象equals的时候,最好重写一下hashCode方法,还记得吧? 为何要这么作? 给你们演示一下,你就能明白了,下面看一个小demo:code

先定义一个Person类:htm

public class Person {

    //身份证
    private String idCard;
    
    private String name;
    
    public String getName() {
        return name;
    }

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

    public void setIdCard(String idCard) {
        this.idCard = idCard;
    }

    //重写equals方法(规则是:idCard一致,则认为是同一我的)
    @Override
    public boolean equals(Object obj) {
        if(obj == this) {
            return true;
        }
        if(!(obj instanceof Person)) {
            return false;
        }
        Person others = (Person) obj;
        if(others.getIdCard().equals(idCard)) {
            return true;
        }
        return false;
    }
}

而后,写一个测试类,用HashSet去添加Person实例:对象

public class Test {

    public static void main(String[] args) {
        
        Person p1 = new Person();
        p1.setIdCard("1234567890");
        
        Person p2 = new Person();
        p2.setIdCard("1234567890");
        
        Set<Person> hashSet = new HashSet<Person>();
        hashSet.add(p1);
        hashSet.add(p2);
        
        System.out.println(hashSet.size());
        
    }
    
}

咱们知道HashSet的元素不可重复,所以,在以上测试代码中,p1 与 p2对象是equals的,咱们原本但愿HashSet只保存其中一个对象, 可是,事与愿违,输出的结果倒是2, 说明hashSet把这两个对象都保存了。这是为何呢? 咱们结合一下HashMap来看吧, 首先,因为咱们没有重写Person的hashCode方法,会致使p1 与 p2的hash值不一致,这时, HashMap会把hash不一致的元素放在不一样的位置, 所以就产生了两个对象。那么,怎么改善? 固然是重写hashCode方法了。下面,咱们在Person类中,重写hashCode方法:blog

@Override
    public int hashCode() {
        return this.idCard.hashCode() * 11;
    }

这时候,咱们再用上面的测试类测试,发现输出为1。OK,终于和咱们的想法一致了。这就是为何强烈推荐在重写equals方法的时候,同时重写hashCode方法的缘由之一。ci

相关文章
相关标签/搜索