Java集合源码剖析——基于JDK1.8中HashSetLinkedHashSet的实现原理
Posted 张起灵-小哥
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java集合源码剖析——基于JDK1.8中HashSetLinkedHashSet的实现原理相关的知识,希望对你有一定的参考价值。
文章目录:
1.开篇
前面三篇文章分别说到了 List 接口中的常用几个实现类:ArrayList、LinkedList、Vector。
而Java集合体系中,List继承了Collection接口,Collection接口又继承了Iterable接口,而在Collection接口的主要的子接口中还有一个兄弟:Set。
这篇文章主要来讲讲Set接口的主要实现类 HashSet、LinkedHashSet的实现原理。那么我们看过部分源码的都知道Set集合的底层实际上就是相应的Map,所以说Set还不如说Map呢,所以这篇文章就简单的对Set集合的源码进行一个分析吧。
2.HashSet中的属性
- HashSet的底层其实就是HashMap(哈希表数据结构),new HashSet的时候实际上就是new了一个HashMap。
- HashSet集合元素的特点:无序、不可重复、没有下标。
- HashSet集合的默认初始化容量是16,默认加载因子是 .75。(HashMap)
//底层使用 HashMap 来保存 HashSet 中所有元素
private transient HashMap<E,Object> map;
//定义一个虚拟的 Object 对象作为 HashMap 的 value,将此对象定义为 static final
private static final Object PRESENT = new Object();
3.HashSet中的方法
3.1 构造方法一
//默认的无参构造器,构造一个空的 HashSet
//实际底层会初始化一个空的 HashMap,并使用默认初始容量为 16 和加载因子 0.75
public HashSet() {
map = new HashMap<>();
}
3.2 构造方法二
//构造一个包含指定 collection 中的元素的新 set
//实际底层使用默认的加载因子 0.75 和足以包含指定 collection 中所有元素的初始容量来创建一个 HashMap
//c 其中的元素将存放在此 set 中的 collection
public HashSet(Collection<? extends E> c) {
map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
addAll(c);
}
3.3 构造方法三
//以指定的 initialCapacity 和 loadFactor 构造一个空的 HashSet
//实际底层以相应的参数构造一个空的 HashMap
//initialCapacity 初始容量,loadFactor 加载因子
public HashSet(int initialCapacity, float loadFactor) {
map = new HashMap<>(initialCapacity, loadFactor);
}
3.4 构造方法四
//以指定的 initialCapacity 构造一个空的 HashSet
//实际底层以相应的参数及加载因子 loadFactor 为 0.75 构造一个空的 HashMap
public HashSet(int initialCapacity) {
map = new HashMap<>(initialCapacity);
}
3.5 构造方法五
//以指定的 initialCapacity 和 loadFactor 构造一个新的空链接哈希集合
//此构造函数为包访问权限,不对外公开,实际只是是对 LinkedHashSet 的支持
//实际底层会以指定的参数构造一个空 LinkedHashMap 实例来实现
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
3.6 迭代器Iterator方法
//返回对此 set 中元素进行迭代的迭代器。返回元素的顺序并不是特定的。
//底层实际调用底层 HashMap 的 keySet 来返回所有的 key
//可见 HashSet 中的元素,只是存放在了底层 HashMap 的 key 上,
//value 使用一个 static final 的 Object 对象标识
public Iterator<E> iterator() {
return map.keySet().iterator();
}
3.7 size方法
//返回此 set 中的元素的数量(set 的容量)
//底层实际调用 HashMap 的 size()方法返回 Entry 的数量,就得到该 Set 中元素的个数
public int size() {
return map.size();
}
3.8 isEmpty方法
//如果此 set 不包含任何元素,则返回 true
//底层实际调用 HashMap 的 isEmpty()判断该 HashSet 是否为空
public boolean isEmpty() {
return map.isEmpty();
}
3.9 contains方法
//如果此 set 包含指定元素,则返回 true
//更确切地讲,当且仅当此 set 包含一个满足(o==null ? e==null : o.equals(e))的 e 元素时,返回 true
//底层实际调用 HashMap 的 containsKey 判断是否包含指定 key
public boolean contains(Object o) {
return map.containsKey(o);
}
3.10 add方法
//如果此 set 中尚未包含指定元素,则添加指定元素
//更确切地讲,如果此 set 没有包含满足(e==null ? e2==null : e.equals(e2))的元素 e2,则向此 set 添加指定的元素 e
//如果此 set 已包含该元素,则该调用不更改 set 并返回 false
//底层实际将将该元素作为 key 放入 HashMap。
//由于 HashMap 的 put()方法添加 key-value 对时,当新放入 HashMap 的 Entry 中 key 与集合中原有 Entry 的 key 相同(hashCode()返回值相等,通过 equals 比较也返回 true)
//新添加的 Entry 的 value 会将覆盖原来 Entry 的 value,但 key 不会有任何改变,
//因此如果向 HashSet 中添加一个已经存在的元素时,新添加的集合元素将不会被放入 HashMap 中,原来的元素也不会有任何改变,这也就满足了 Set 中元素不重复的特性
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
3.11 remove方法
//如果指定元素存在于此 set 中,则将其移除
//更确切地讲,如果此 set 包含一个满足(o==null ? e==null : o.equals(e))的元素e,则将其移除。如果此 set 已包含该元素,则返回 true
//(或者:如果此 set 因调用而发生更改,则返回 true)。(一旦调用返回,则此 set 不再包含该元素)。
//底层实际调用 HashMap 的 remove 方法删除指定 Entry
public boolean remove(Object o) {
return map.remove(o)==PRESENT;
}
3.12 clear方法
//从此 set 中移除所有元素。此调用返回后,该 set 将为空
//底层实际调用 HashMap 的 clear 方法清空 Entry 中所有元素
public void clear() {
map.clear();
}
4.LinkedHashSet中的方法
LinkedHashSet 是 HashSet 的一个子类。LinkedHashSet 是具有可预知迭代顺序的 Set 接口的哈希表和链接列表实现。此实现与HashSet 的不同之处在于,后者维护着一个运行于所有条目的双重链接列表。此链接列表定义了迭代顺序,该迭代顺序可为插入顺序或是访问顺序。
注意,此实现不是同步的。如果多个线程同时访问链接的哈希 Set,而其中至少一个线程修改了该 Set,则它必须保持外部同步。
对于 LinkedHashSet 而言,它继承与 HashSet、又基于 LinkedHashMap 来实现的。LinkedHashSet 底层使用 LinkedHashMap 来保存所有元素,它继承与 HashSet,其所有的方法操作上又与 HashSet 相同,因此 LinkedHashSet 的实现上非常简单,只提供了四个构造方法,并通过传递一个标识参数dummy,调用父类的构造器,底层构造一个 LinkedHashMap来实现,在相关操作上与父类 HashSet 的操作相同,直接调用父类 HashSet 的方法即可。
//构造一个带有指定初始容量和加载因子的新空链接哈希 set
//底层会调用父类的构造方法,构造一个有指定初始容量和加载因子的 LinkedHashMap 实例
initialCapacity 初始容量,loadFactor 加载因子
public LinkedHashSet(int initialCapacity, float loadFactor) {
super(initialCapacity, loadFactor, true);
}
//构造一个带指定初始容量和默认加载因子 0.75 的新空链接哈希 set
//底层会调用父类的构造方法,构造一个带指定初始容量和默认加载因子 0.75 的 LinkedHashMap 实例
public LinkedHashSet(int initialCapacity) {
super(initialCapacity, .75f, true);
}
//构造一个带默认初始容量 16 和加载因子 0.75 的新空链接哈希 set
//底层会调用父类的构造方法,构造一个带默认初始容量 16 和加载因子 0.75 的 LinkedHashMap 实例
public LinkedHashSet() {
super(16, .75f, true);
}
//构造一个与指定 collection 中的元素相同的新链接哈希 set
//底层会调用父类的构造方法,构造一个足以包含指定 collection中所有元素的初始容量和加载因子为 0.75 的 LinkedHashMap 实例
//c 其中的元素将存放在此 set 中的 collection
public LinkedHashSet(Collection<? extends E> c) {
super(Math.max(2*c.size(), 11), .75f, true);
addAll(c);
}
以上是关于Java集合源码剖析——基于JDK1.8中HashSetLinkedHashSet的实现原理的主要内容,如果未能解决你的问题,请参考以下文章
Java集合源码剖析——基于JDK1.8中ConcurrentHashMap的实现原理
Java集合源码剖析——基于JDK1.8中ConcurrentHashMap的实现原理
Java集合源码剖析——基于JDK1.8中LinkedList的实现原理
Java集合源码剖析——基于JDK1.8中LinkedList的实现原理