首页 > 学院 > 开发设计 > 正文

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

2019-11-14 21:21:12
字体:
来源:转载
供稿:网友
HashSet其实就那么一回事儿之源码浅析

上篇文章《HashMap其实就那么一回事儿之源码浅析》介绍了hashMap, 本次将带大家看看HashSet, HashSet其实就是基于HashMap实现, 因此,熟悉了HashMap, 再来看HashSet的源码,会觉得极其简单。下面还是直接看源码吧:

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的源码作一个总结吧:

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

2. 根据HashMap的特性,可以推敲出:HashSet允许拥有1个为null的值, HashSet的值不可重复。

3. 在创建HashSet的时候,如果合适,最好指定其内部HashMap的 capacity和loadFactory的值, 至于原因,在介绍HashMap的时候,提到过。

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

先定义一个Person类:

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方法:

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

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

好了,本次就写到此。谢谢大家!


发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表