简介

HashSet就是一个集合,里面不能有重复的元素,元素也是无序的。
HashSet其实就是调用了HashMap实现的,所以,它也不是线程安全的。
HashSet通过iterator()返回的迭代器是fail-fast的。

源码分析

由于HashSet的源码很短也很容易理解,这里就不再选取分析了。


package java.util; import java.io.InvalidObjectException; public class HashSet<E>
extends AbstractSet<E>
implements Set<E>, Cloneable, java.io.Serializable
{
static final long serialVersionUID = -5024744406713321676L;
// 内部的HashMap,后面的操作都是对它的调用
private transient HashMap<E,Object> map;
// 用于插入HashMap时的固定值
private static final Object PRESENT = new Object(); // 默认构造
public HashSet() {
map = new HashMap<>();
}
// 填入集合的构造
public HashSet(Collection<? extends E> c) {
// 这里为什么需要在原来的容量和16中取最大值呢?看总结
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);
}
// 采用LinkedHashMap来实现
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
// 获取迭代器
public Iterator<E> iterator() {
return map.keySet().iterator();
} 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();
} // 克隆函数,直接用map的clone函数,将原来的map克隆到新的map
@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);
}
} // 将set输出为序列化
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);
} // 将输入写入set
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);
} // 计算容量
capacity = (int) Math.min(size * Math.min(1 / loadFactor, 4.0f),
HashMap.MAXIMUM_CAPACITY); // 创建一个新的内部map
map = (((HashSet<?>)this) instanceof LinkedHashSet ?
new LinkedHashMap<E,Object>(capacity, loadFactor) :
new HashMap<E,Object>(capacity, loadFactor)); // 依次写入集合元素
for (int i=0; i<size; i++) {
@SuppressWarnings("unchecked")
E e = (E) s.readObject();
map.put(e, PRESENT);
}
} // 实现了分割迭代,多线程用
public Spliterator<E> spliterator() {
return new HashMap.KeySpliterator<E,Object>(map, 0, -1, 0, 0);
}
}

总结

HashSet内部都是对HashMap的调用,不同的Key值,相同的value值。
可以通过迭代器和for-each方法对其进行遍历。

为什么在初始化的时候有map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));

  • 首先HashMap的默认初始容量是16,如果填入集合的大小经过变换之后还小于默认初始容量,那么就直接使用默认初始容量。
  • HashMap的默认加载因子就是0.75,当HashMap的“阈值”(阈值=HashMap总的大小*加载因子) < “HashMap实际大小”时,就会将容量翻倍,所以 c.size()/.75f) + 1 计算出来的正好是总的空间大小。

Java集合源码分析(九)——HashSet的更多相关文章

  1. java集合源码分析(三):ArrayList

    概述 在前文:java集合源码分析(二):List与AbstractList 和 java集合源码分析(一):Collection 与 AbstractCollection 中,我们大致了解了从 Co ...

  2. java集合源码分析(六):HashMap

    概述 HashMap 是 Map 接口下一个线程不安全的,基于哈希表的实现类.由于他解决哈希冲突的方式是分离链表法,也就是拉链法,因此他的数据结构是数组+链表,在 JDK8 以后,当哈希冲突严重时,H ...

  3. Java 集合源码分析(一)HashMap

    目录 Java 集合源码分析(一)HashMap 1. 概要 2. JDK 7 的 HashMap 3. JDK 1.8 的 HashMap 4. Hashtable 5. JDK 1.7 的 Con ...

  4. Java集合源码分析(四)Vector<E>

    Vector<E>简介 Vector也是基于数组实现的,是一个动态数组,其容量能自动增长. Vector是JDK1.0引入了,它的很多实现方法都加入了同步语句,因此是线程安全的(其实也只是 ...

  5. Java集合源码分析(三)LinkedList

    LinkedList简介 LinkedList是基于双向循环链表(从源码中可以很容易看出)实现的,除了可以当做链表来操作外,它还可以当做栈.队列和双端队列来使用. LinkedList同样是非线程安全 ...

  6. Java集合源码分析(二)ArrayList

    ArrayList简介 ArrayList是基于数组实现的,是一个动态数组,其容量能自动增长,类似于C语言中的动态申请内存,动态增长内存. ArrayList不是线程安全的,只能用在单线程环境下,多线 ...

  7. java集合源码分析几篇文章

    java集合源码解析https://blog.csdn.net/ns_code/article/category/2362915

  8. Java集合源码分析(五)HashSet<E>

    HashSet简介 HashSet实现Set接口,由哈希表(实际上是一个HashMap实例)支持.它不保证set 的迭代顺序:特别是它不保证该顺序恒久不变.此类允许使用null元素. HashSet源 ...

  9. Java集合源码分析(六)TreeSet<E>

    TreeSet简介 TreeSet 是一个有序的集合,它的作用是提供有序的Set集合.它继承于AbstractSet抽象类,实现了NavigableSet<E>, Cloneable, j ...

随机推荐

  1. DB2 建表,添加字段,删除字段,修改字段等常用操作

    转载:http://blog.sina.com.cn/s/blog_67aaf4440100v01p.html,稍作修改. --创建数据库 create database Etp; --连接数据库 c ...

  2. 12.java设计模式之代理模式

    基本介绍: 代理模式(Proxy)为一个对象提供一个替身,以控制对这个对象的访问.即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能,想在 ...

  3. Spark SQL | 目前Spark社区最活跃的组件之一

    Spark SQL是一个用来处理结构化数据的Spark组件,前身是shark,但是shark过多的依赖于hive如采用hive的语法解析器.查询优化器等,制约了Spark各个组件之间的相互集成,因此S ...

  4. Cypress系列(90)- Cypress.Cookies 命令详解以及如何跨测试用例共享 Cookies

    如果想从头学起Cypress,可以看下面的系列文章哦 https://www.cnblogs.com/poloyy/category/1768839.html Cypress.Cookies 共有三个 ...

  5. NPOI读写Excel组件封装Excel导入导出组件

    后台管理系统多数情况会与Excel打交道,常见的就是Excel的导入导出,对于Excel的操作往往是繁琐且容易出错的,对于后台系统的导入导出交互过程往往是固定的,对于这部分操作,我们可以抽离出公共组件 ...

  6. 如何使用ABBYY FineReader 处理无法识别的字符?

    在识别PDF文档时,我们可能会遇到文档中存在多种语言.多种不同类型文字字符的情况.在ABBYY FineReader 15(Windows系统)OCR文字识别软件的默认语言数据下,可能无法识别PDF文 ...

  7. 关于ABBYY的常见问题与解答

    问:ABBYY的版本那么多,我不知道哪款是我需要的.可不可以帮助我选择? 答:您可在此查看不同版本的功能介绍与版本对比,选择适合自己的版本即可. 查看ABBYY FineReader 15功能:查看A ...

  8. 使用iMindMap思维导图软件的活动策划模板制定策划方案

    活动策划不单单是一个头脑风暴的过程,更是一个整合各项资源.条件的过程.因此我们可以合理的使用思维导图软件来做活动策划.iMindMap(Windows系统)思维导图软件提供了快捷而方便的活动策划模板, ...

  9. docker中启动容器提示端口被占用

    docker中启动容器提示端口被占用,但是 docker ps -a 查不到信息 1.查询端口被占用的id netstat -ntpl |grep 3306 2.杀掉该id kill -9 如果kil ...

  10. python办公入门4:xlrd操作excel行

    操作excel行 1 #通过索引获取操作行 2 sheet=data.sheet_by_index(0) 3 #获取当前sheet下的有效行数 4 print(sheet.nrows) 5 #获取某一 ...