jdk1.8.0_45源码解读——HashSet的实现
jdk1.8.0_45源码解读——HashSet的实现
一、HashSet概述
HashSet实现Set接口,由哈希表(实际上是一个HashMap实例)支持。主要具有以下的特点:
- 不保证set的迭代顺序,特别是它不保证该顺序恒久不变
- 有且只允许一个null元素
- 不允许有重复元素,这是因为HashSet是基于HashMap实现的,HashSet中的元素都存放在HashMap的key上面,而value中的值都是统一的一个private static final Object PRESENT = new Object();
- 非同步的。如果多 个线程同时访问一个哈希set,而其中至少一个线程修改了该 set,那么它必须保持外部同步。这通常是通过对自然封装该set的对象执行同步操作来完成的。如果不存在这样的对象,则应该使用 Collections.synchronizedSet 方法来“包装” set。最好在创建时完成这一操作,以防止对该set进行意外的不同步访问:
Set s = Collections.synchronizedSet(new HashSet(...));
- HashSet通过iterator()返回的迭代器是fail-fast的
二、HashSet源码解析
相关HashSet的操作,基本上都是直接调用底层HashMap的相关方法来完成。
1. HashSet类结构
//通过HashSet实现的接口可知,其支持所有集合操作,能被克隆,支持序列化
public class HashSet<E>
extends AbstractSet<E>
implements Set<E>, Cloneable, java.io.Serializable
{
static final long serialVersionUID = -5024744406713321676L; private transient HashMap<E,Object> map; //定义一个"虚拟"的static final Object对象作为HashMap的value
private static final Object PRESENT = new Object(); ......
}
HashSet包含了两个重要的成员变量:map, PRESENT。
(01) map是一个HashMap<E, Object>对象,HashSet是由一个HashMap实例支持的。
(02) PRESENT是一个static final Object对象,用来作为HashMap中的value值。
2. 构造函数
HashSet提供了四种方式的构造器,可以构造一个新的空 set,其底层 HashMap实例的默认初始容量是16,加载因子是 0.75,构造一个包含指定collection中的元素的新set,构造一个新的空set,其底层HashMap实例具有指定的初始容量和默认的加载因子(0.75),以及构造一个新的空set,其底层HashMap实例具有指定的初始容量和指定的加载因子。
//默认的无参构造器,构造一个空的HashSet,实际底层会初始化一个空的HashMap,并使用默认初始容量为16和加载因子0.75。
public HashSet() {
map = new HashMap<E, Object>();
} //构造一个包含指定collection中的元素的新set。
//实际底层使用默认的加载因子0.75和足以包含指定collection中所有元素的初始容量来创建一个HashMap。
public HashSet(Collection<? extends E> c) {
map = new HashMap<E, Object>(Math.max((int) (c.size()/.75f) + 1, 16));
addAll(c); //AbstractCollection.addAll(Collection<? extends E> c)
} //以指定的初始容量和加载因子构造一个空的HashSet
public HashSet(int initialCapacity, float loadFactor) {
map = new HashMap<E, Object>(initialCapacity, loadFactor);
} //以指定的initialCapacity和默认加载因子0.75构造一个空的HashSet
public HashSet(int initialCapacity) {
map = new HashMap<E, Object>(initialCapacity);
} /**
* 以指定的initialCapacity和loadFactor构造一个新的空链接哈希集合
* 此构造函数为包访问权限,不对外公开,实际只是是对LinkedHashSet的支持
*
* @param initialCapacity 初始容量
* @param loadFactor 加载因子
* @param dummy 标记,用于与其他的构造函数区分(可忽略)
* @throws IllegalArgumentException 如果初始容量小于零或加载因子为非正数
*/
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<E, Object>(initialCapacity, loadFactor);
}
3.查找
HashSet提供了contains(Object o)查看是否包含指定元素的方法,其底层调用的是HashMap.containsKey(Object key)判断是否包含指定key。
//如果此set包含指定元素,则返回 true
public boolean contains(Object o) {
return map.containsKey(o);
}
4.添加
HashSet提供了add(E e)添加元素的方法,其调用的是底层HashMap中的put(K key, V value)方法,首先判断元素(也就是key)是否存在,如果不存在则插入,如果存在则不插入,这样HashSet中就不存在重复值。
//如果此set中尚未包含指定元素,则添加指定元素
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
底层:当向HashSet集合中存入一个元素时,HashSet会调用该对象的hashCode()方法来得到该对象的hashCode值,然后根据该值确定对象在HashSet中的存储位置。在Hash集合中,不能同时存放两个相等的元素,而判断两个元素相等的标准是两个对象通过equals方法比较相等并且两个对象的HashCode方法返回值也相等。
注意:对于HashSet中保存的对象,请注意正确重写其equals和hashCode方法,以保证放入的对象的唯一性。
5.清空与删除
HashSet提供了remove(Object o)删除元素、clear()清除所有元素的方法。
//如果指定元素存在于此set中,则将其移除
public boolean remove(Object o) {
return map.remove(o)==PRESENT;
} //从此set中移除所有元素
public void clear() {
map.clear();
}
6.其他公开的方法
size()、isEmpty()、clone()
//返回此set中的元素的数量
public int size() {
return map.size();
} //如果此set不包含任何元素,则返回 true
public boolean isEmpty() {
return map.isEmpty();
} //返回此 HashSet实例的浅表副本
@SuppressWarnings("unchecked")
public Object clone() {
try {
HashSet<E> newSet = (HashSet<E>) super.clone();
newSet.map = (HashMap<E, Object>) map.clone();
return newSet;
} catch (CloneNotSupportedException e) {
throw new InternalError(e);
}
}
支持序列化的写入函数writeObject(java.io.ObjectOutputStream s)和读取函数readObject(java.io.ObjectInputStream s):
//java.io.Serializable的写入函数,将HashSet的“总的容量,加载因子,实际容量,所有的元素”都写入到输出流中
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException {
// Write out any hidden serialization magic
s.defaultWriteObject(); // Write out HashMap capacity and load factor
s.writeInt(map.capacity());
s.writeFloat(map.loadFactor()); // Write out size
s.writeInt(map.size()); // Write out all elements in the proper order.
for (E e : map.keySet())
s.writeObject(e);
} // java.io.Serializable的读取函数,将HashSet的“总的容量,加载因子,实际容量,所有的元素”依次读出
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// Read in any hidden serialization magic
s.defaultReadObject(); // Read capacity and verify non-negative.
int capacity = s.readInt();
if (capacity < 0) {
throw new InvalidObjectException("Illegal capacity: " +
capacity);
} // Read load factor and verify positive and non NaN.
float loadFactor = s.readFloat();
if (loadFactor <= 0 || Float.isNaN(loadFactor)) {
throw new InvalidObjectException("Illegal load factor: " +
loadFactor);
} // Read size and verify non-negative.
int size = s.readInt();
if (size < 0) {
throw new InvalidObjectException("Illegal size: " +
size);
} // Set the capacity according to the size and load factor ensuring that
// the HashMap is at least 25% full but clamping to maximum capacity.
capacity = (int) Math.min(size * Math.min(1 / loadFactor, 4.0f),
HashMap.MAXIMUM_CAPACITY); // Create backing HashMap
map = (((HashSet<?>)this) instanceof LinkedHashSet ?
new LinkedHashMap<E,Object>(capacity, loadFactor) :
new HashMap<E,Object>(capacity, loadFactor)); // Read in all elements in the proper order.
for (int i=0; i<size; i++) {
@SuppressWarnings("unchecked")
E e = (E) s.readObject();
map.put(e, PRESENT);
}
}
三、HashSet的迭代
HashSet通过调用HashMap.keySet()返回<key, value>对中的key集以此得到集合的迭代器。
//返回对此 set中元素进行迭代的迭代器
public Iterator<E> iterator() {
return map.keySet().iterator(); //HashMap.keySet()返回<key, value>对中的key集
}
jdk1.8.0_45源码解读——HashSet的实现的更多相关文章
- jdk1.8.0_45源码解读——Set接口和AbstractSet抽象类的实现
jdk1.8.0_45源码解读——Set接口和AbstractSet抽象类的实现 一. Set架构 如上图: (01) Set 是继承于Collection的接口.它是一个不允许有重复元素的集合.(0 ...
- jdk1.8.0_45源码解读——HashMap的实现
jdk1.8.0_45源码解读——HashMap的实现 一.HashMap概述 HashMap是基于哈希表的Map接口实现的,此实现提供所有可选的映射操作.存储的是<key,value>对 ...
- jdk1.8.0_45源码解读——Map接口和AbstractMap抽象类的实现
jdk1.8.0_45源码解读——Map接口和AbstractMap抽象类的实现 一. Map架构 如上图:(01) Map 是映射接口,Map中存储的内容是键值对(key-value).(02) A ...
- jdk1.8.0_45源码解读——LinkedList的实现
jdk1.8.0_45源码解读——LinkedList的实现 一.LinkedList概述 LinkedList是List和Deque接口的双向链表的实现.实现了所有可选列表操作,并允许包括null值 ...
- jdk1.8.0_45源码解读——ArrayList的实现
jdk1.8.0_45源码解读——ArrayList的实现 一.ArrayList概述 ArrayList是List接口的可变数组的实现.实现了所有可选列表操作,并允许包括 null 在内的所有元素. ...
- JDK1.7.0_45源码阅读<java.lang.Boolean>
本文适合的人群 其实感觉写这个标题的内容没有必要,只要你觉得对你有帮助那么就适合你,对你没帮助那么就不适合你.毕竟我不是专业作者,但咱会尽力的.其实最重要的一点是我不希望浪费您宝贵时间. 简要把内容在 ...
- 学习JDK1.8集合源码之--HashSet
1. HashSet简介 HashSet是一个不可重复的无序集合,底层由HashMap实现存储,故HashSet是非线程安全的,由于HashSet使用HashMap的Key来存储元素,而HashMap ...
- JDK容器类List,Set,Queue源码解读
List,Set,Queue都是继承Collection接口的单列集合接口.List常用的实现主要有ArrayList,LinkedList,List中的数据是有序可重复的.Set常用的实现主要是Ha ...
- 【原】Spark中Job的提交源码解读
版权声明:本文为原创文章,未经允许不得转载. Spark程序程序job的运行是通过actions算子触发的,每一个action算子其实是一个runJob方法的运行,详见文章 SparkContex源码 ...
随机推荐
- VirtualBox虚拟机怎么导入已经存在的vdi文件
VirtualBox虚拟机怎么导入已经存在的vdi文件 第一章 1.原因 早上一不小心将virtualBox 卸载了,(不知道怎么了, 里面得虚拟机全部都没有了,但是vdi文件还在) 2.解决办法 直 ...
- 动态绑定,多态,static
1:下面程序的输出结果为: class base2{ static{ System.out.println("static base2"); } } public class te ...
- alpha阶段的 postmortem 报告
1. 每个成员到了第二次alpha 阶段与第一次相比,取得什么进步? 成员 黄杰 学会了app环境的搭建和代码的基本理解 李炫宗 更加明白安卓代码的编写和理解 康取 对安卓界面的设计有一些了解 ...
- Linux 下安装nginx的总结 (之前写的有问题))
1. 下载niginx的 tar包 下载路径 http://nginx.org/en/download.html 也可以直接使用命令下载 wget http://nginx.org/download/ ...
- 一本通1645Fibonacci
1645:Fibonacci 时间限制: 1000 ms 内存限制: 524288 KB [题目描述] 原题来自:POJ 3070 我们知道斐波那契数列 F0=0,F1=1,Fn=Fn ...
- MySQL 5.7基于组提交的并行复制
参考链接: http://mysql.taobao.org/monthly/2016/08/01/ https://www.kancloud.cn/thinkphp/mysql-parallel-ap ...
- Tour HDU - 3488(最大权值匹配)
Tour In the kingdom of Henryy, there are N (2 <= N <= 200) cities, with M (M <= 30000) one- ...
- 【刷题】LOJ 6121 「网络流 24 题」孤岛营救问题
题目描述 1944 年,特种兵麦克接到国防部的命令,要求立即赶赴太平洋上的一个孤岛,营救被敌军俘虏的大兵瑞恩.瑞恩被关押在一个迷宫里,迷宫地形复杂,但幸好麦克得到了迷宫的地形图.迷宫的外形是一个长方形 ...
- springboot中radis配置和使用【进阶二】
1.yml文件配置 #redis哨兵模式配置 redis: namespace: xxx:xxx:redis connection: cacheRedis: database: 0 timeout: ...
- 如何设置Java虚拟机JVM启动内存参数
Tomcat默认的Java虚拟机JVM启动内存参数大约只有64MB或者128MB,非常小,远远没有利用现在服务器的强大内存,所以要设置Java虚拟机JVM启动内存参数.具体设置方法为: Tomcat修 ...