《Java源码分析》:HashSet
前面几篇把Map、List相关的的几个类看了下,当然Set接口相关的类也相当的重要,我们也用的比较多,因此,我们也需要了解下Set接口的两个实现类HashSet、TreeSet的源码。这篇博文是记录HashSet的源码分析。下篇博文就来记录TreeSet的源码分析。
如果想了解其他的源码分析,可点击下面相对应的连接哈。
HashMap类的源码分析在这里:http://blog.csdn.net/u010412719/article/details/51980632
Hashtable类的源码分析在这里:http://blog.csdn.net/u010412719/article/details/51972602
LinkedHashMap类的源码分析在这里:http://blog.csdn.net/u010412719/article/details/51984455
Vector类的源码分析在这里:http://blog.csdn.net/u010412719/article/details/51992819
Stack类的源码分析在这里:http://blog.csdn.net/u010412719/article/details/51995168
ArrayList类的源码分析在这里:http://blog.csdn.net/u010412719/article/details/51108357
LinkedList类的源码分析在这里:http://blog.csdn.net/u010412719/article/details/51124350
说明:我们都知道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
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
在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);
}
3、HashSet中常见的一些方法
3.1、 add(E e)
源码如下:
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
看到这个源码是不是很爽,太简单了哈,就是将要添加的元素作为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();
}
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();
}
小结
确实比较简单哈,关于HashSet的内部实现我们只需要记住一点:是基于HashMap来实现的,即所有方法都是委托给HashMap对象来实现的;在使用方面,也只需要记住一点:所有Set集合对象存储的元素是不允许重复的哈