~ 前言
讲完前面的HashMap和LinkedHashMap之后接下来就是Set了,这里只讲两个(HashSet与LinkedHashSet)。
后续讲解内容为源码实现,这里使用的是JDK8的版本。
HashSet
HashSet类,是存在于java.util包中的类 。同时也被称为集合,该容器中只能存储不重复的对象。
对于 HashSet 而言,它是基于 HashMap 实现的,底层采用 HashMap 来保存元素,所以如果对 HashMap与LinkedHashMap 比较熟悉了,那么学习 HashSet 也是很轻松的。
先来认识一下很重要的两个成员。
// 实际存储数据结构
private transient HashMap<E,Object> map;
// 存入HashMap的value
private static final Object PRESENT = new Object();
然后我们来看一下它的关系图。
通过关系图我们能清楚的看到它的实现:
- iterable,可以使用迭代器
- collection,有add、remove等方法
- cloneable,可以克隆
- serializable,可以被序列化
初始化
简单分析一下:
// 默认初始化存储结构
public HashSet() {
map = new HashMap<>();
}
// 根据传入集合存储到HashMap中
public HashSet(Collection<? extends E> c) {
map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
addAll(c);
}
// 根据传入值自定义HashMap
public HashSet(int initialCapacity, float loadFactor) {
map = new HashMap<>(initialCapacity, loadFactor);
}
// 根据传入值自定义HashMap
public HashSet(int initialCapacity) {
map = new HashMap<>(initialCapacity);
}
// 基于LinkedHashMap为存储数据实现
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
这样就看完了初始化的代码,是不是很简单呢?
~ 基本方法解析
add
在传入集合的初始化方法中有调用到一个方法 addAll(),而底层就是调用了add()方法。
// 调用add()方法
public boolean addAll(Collection<? extends E> c) {
boolean modified = false;
for (E e : c)
if (add(e))
modified = true;
return modified;
}
// 调用HashMap或LinkedHashMap的put()方法
// PRESENT = new Object();
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
非常简单。
remove
public boolean remove(Object o) {
return map.remove(o)==PRESENT;
}
也是调用了Map的方法,到这里我们发现基本上所有方法都是对Map存储结构进行封装,那么剩下的就交给同学们继续看了。
LinkedHashSet
LinkedHashSet集合同样是根据元素的hashCode值来决定元素的存储位置,但是它同时使用链表维护元素的次序,保持元素的添加顺序。LinkedHashSet在迭代访问Set中的全部元素时,性能比HashSet好,但是插入时性能稍微逊色于HashSet。我们先看一下它的关系图。
我们可以发现它继承至HashSet,也就说它基于HashSet来实现的,那么为什么说插入时性能稍微逊色于HashSet?移除与获取节点也会逊色于HashSet?
因为我们可以看到LinkedHashSet都是以LinkedHashMap为数据结构来实现的,而HashSet可以进行选择HashMap或LinkedHashMap。所以我们实际上是对于底层的数据结构进行分析,就像数据库一样,存储与查询的性能都是依赖于底层的数据结构实现与优化。那我们对比一下LinkedHashMap与HashMap两个实现会发现,LinkedHashMap在调用基本API方法时都会有回调方法来维护本身的链表,这里就造成了多于HashMap的性能开销,所以这里得出的结论就是性能可能稍微逊色与HashSet。
~ 初始化
public LinkedHashSet(int initialCapacity, float loadFactor) {
super(initialCapacity, loadFactor, true);
}
public LinkedHashSet(int initialCapacity) {
super(initialCapacity, .75f, true);
}
public LinkedHashSet() {
super(16, .75f, true);
}
public LinkedHashSet(Collection<? extends E> c) {
super(Math.max(2*c.size(), 11), .75f, true);
addAll(c);
}
上面的4个初始化方法都是直接调用了HashSet的初始化方法
// 基于LinkedHashMap为存储数据实现
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
}
然后我们查看源码可以发现所有的基础api都是直接调用了HashSet的方法,所以到这里我们就把LinkedHashSet看完了。
最后
这一章可以说讲解了个寂寞,十分简单。
如果有些同学不了解实现原理的话,就需要先看一下前面的文章(HashMap、LinkedHashMap)。