简介

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. linux 命令之file

    Linux file命令用于辨识文件类型. 通过file指令,我们得以辨识该文件的类型. 语法 file [-bcLvz][-f <名称文件>][-m <魔法数字文件>...] ...

  2. linux的别名(alias/unalias)

    linux中有别名时先找的别名后找命令文件 临时创建是直接用alias. [root@localhost ~]# alias ls=pwd [root@localhost ~]# ls /root 其 ...

  3. 05 . Vue前端交互,fetch,axios,以asyncawait方式调用接口使用及案例

    目标 /* 1. 说出什么是前后端交互模式 2. 说出Promise的相关概念和用法 3. 使用fetch进行接口调用 4. 使用axios进行接口调用 5. 使用asynnc/await方式调用接口 ...

  4. custom-ubuntu-server-iso

    Remastering the Ubuntu Desktop ISO is easy considering the existing graphical tools but did you ever ...

  5. 面试常问的 25+ 个 Linux 命令

    作为一个Java开发人员,有些常用的Linux命令必须掌握.即时平时开发过程中不使用Linux(Unix)或者mac系统,也需要熟练掌握Linux命令.因为很多服务器上都是Linux系统.所以,要和服 ...

  6. JVM学习之Java技术体系

    目录 一.Java技术体系 1.Java体系构成 2.JDK.JRE.JVM之前的关系 JVM介绍 (1)JVM官方文档定义 (2)中文解释 JVM结构 Java代码执行流程 JVM架构模型 1.指令 ...

  7. PVE简单迁移虚拟机

    工作中有2台PVE节点,但是没有做集群,如果有集群可以很方便的进行迁移.本次迁移的目的是: 目前有一台PVE1节点装的虚机资源使用较多,想迁移某台虚机到另一台PVE2. 1 备份 备份在web页面操作 ...

  8. RayFire的下载与安装方法

    RayFire的下载与安装方法 发布时间:2020/10/12 近几年,电影中融入了越来越多的动画元素,其中的爆炸场景更是十分吸引眼球.小编不禁好奇,什么样的插件能做出来如此好玩的特效,上网搜索一番发 ...

  9. FL Studio中echo的延迟作用

    今天来一起研究FL Studio的Echo Delay的作用,Echo Delay可以从MIDI输入创建回声,并允许我们通过音量,声像,切除和共振,音高和时间来操纵延迟. 图1:Echo Dealy ...

  10. 如何用MathType 7输入连续几个数的和

    在数学的学习中,我们经常需要使用求和符合来求连续几个数的和,那么作为专业的公式编辑器,如何输入连续几个数的求和呢? 具体步骤如下: 步骤一 打开专业的公式编辑软件MathType 7,用鼠标点击上方的 ...