《Java源码分析》:HashSet

来源:互联网 发布:网络诈骗案例大全 编辑:程序博客网 时间:2024/05/20 07:33

说明:我们都知道HashSet集合是不允许重复元素的,因此这个类的利用什么来保证这个集合里面不会有重复的元素呢?结论是:HashSet是借助于HashMap的key不允许重复这个特性来实现的。想想这个思想确实比较奇妙,是吧,HashMap是操作键值对,而HashSet是操作HashMap的key完成相关操作,或者这么说,HashSet全部的操作是借助于HashMap经过某种封装得到的

下面来开始具体的分析,其实,如果我们了解HashMap的内部实现,HashSet的内部实现就相当的容易理解。

1、HashSet的继承结构

    public class HashSet<E>        extends AbstractSet<E>        implements Set<E>, Cloneable, java.io.Serializable
  • 1
  • 2
  • 3

HashSet继承了AbstractSet抽象类,也实现了Set、Cloneable和Serializable接口。

或许你可能有这样的疑惑:AbstractSet实现了Set接口,HashSet既要继承AbstractSet还需要实现Set接口呢??

如果你看过HashMap的源码,HashMap的继承结构和HashSet类似,,为了方便比较,还是贴一下:

    public class HashMap<K,V> extends AbstractMap<K,V>        implements Map<K,V>, Cloneable, Serializable 
  • 1
  • 2
  • 3

在HashMap我们已经探讨了为什么AbstractMap实现了Map接口,HashMap又继承AbstractMap还要实现Map接口呢??

这个问题在HashMap源码分析中已经有了答案,因此,HashSet的继承结构为什么如此,我想原因应该是一样的,HashSet之所以还实现Set接口,在功能上没有任何作用,仅仅只是使结构体系更加清晰完整。

2、HashSet的属性和构造函数

从下面可以看出,HashSet有一个HashMap的引用,且有一个常量。

HashSet的构造函数,都是创建一个map实例对象。HashSet对象的默认大小与HashMap默认大小一样,是16,且大小只能是2的幂次方。

    private transient HashMap<E,Object> map;    // Dummy value to associate with an Object in the backing Map    private static final Object PRESENT = new Object();    /**     * Constructs a new, empty set; the backing <tt>HashMap</tt> instance has     * default initial capacity (16) and load factor (0.75).     */    public HashSet() {        map = new HashMap<>();    }    public HashSet(Collection<? extends E> c) {        map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));        addAll(c);    }    public HashSet(int initialCapacity, float loadFactor) {        map = new HashMap<>(initialCapacity, loadFactor);    }    public HashSet(int initialCapacity) {        map = new HashMap<>(initialCapacity);    }    /**     * Constructs a new, empty linked hash set.  (This package private     * constructor is only used by LinkedHashSet.) The backing     * HashMap instance is a LinkedHashMap with the specified initial     * capacity and the specified load factor.     *     */    HashSet(int initialCapacity, float loadFactor, boolean dummy) {        map = new LinkedHashMap<>(initialCapacity, loadFactor);    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

3、HashSet中常见的一些方法

3.1、 add(E e)

源码如下:

    public boolean add(E e) {        return map.put(e, PRESENT)==null;    }
  • 1
  • 2
  • 3
  • 4

看到这个源码是不是很爽,太简单了哈,就是将要添加的元素作为map的key,常量PRESENT作为value保存在HashMap对象中即可。有一点需要我们注意的是:这个add方法有返回值,返回值有map的put方法决定,当map调用put(key,value)方法时,如果存在key后,则map的put方法会返回此key对应的oldValue。此时add就会返回false

3.2、 iterator()

HashSet并没有提供类似于get的方法,只提供的获取访问元素的迭代器对象。iterator方法也是委托给了map,获取map的key集合的迭代器。

    public Iterator<E> iterator() {        return map.keySet().iterator();    }
  • 1
  • 2
  • 3
  • 4

3.3、 其它的方法

其它方法的源码如下,全部都是委托给HashMap来实现的,是不是很简单哈,这里就不再过多的进行分析。

    public int size() {        return map.size();    }    public boolean isEmpty() {        return map.isEmpty();    }    public boolean contains(Object o) {        return map.containsKey(o);    }    public boolean add(E e) {        return map.put(e, PRESENT)==null;    }    public boolean remove(Object o) {        return map.remove(o)==PRESENT;    }    public void clear() {        map.clear();    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

小结

确实比较简单哈,关于HashSet的内部实现我们只需要记住一点:是基于HashMap来实现的,即所有方法都是委托给HashMap对象来实现的;在使用方面,也只需要记住一点:所有Set集合对象存储的元素是不允许重复的哈