HashSet源码解析

HashSet简单使用的demo

   public static void main(String[] args) {
Set<String> set = new HashSet<>();
set.add("中国");
set.add("你好");
set.add("中国");
System.out.println(set);
}

运行结果如下:



可以看到,两次add相同的值,最后只有一个添加成功了。

所以,set和list的主要区别就是不重复,而且set里面的元素是无序的,hashSet是线程不安全的

HashSet中的变量

    //用来存放set进来的值
private transient HashMap<E,Object> map; //PRESENT是每次add元素的时候,做为map的value值,
//这样的话,key是不一样的,可以保证不重复
//PRESENT做为value值,这样可以使得hashSet只用HashMap的key存储
private static final Object PRESENT = new Object();

hashSet底层是使用hashMap来存储元素的,hashMap是以键值对的方式存储,而set存储的是单个元素,所以PRESENT做为value值,是一个常量,这样可以保证set的存储是单个元素。

HashSet的构造函数

   //构建一个空的hashSet,由于是基于hashMap实现的
//所以默认初始容量是16,负载因子是0.75
public HashSet() {
map = new HashMap<>();
} /**
* 这个构造函数,是把一个集合放入HashSet中
* 默认初始容量是16,负载因子是0.75
* @param c 要放入HashSet的元素
* @throws NullPointerException 如果传入的集合是空的,就抛异常
*/
public HashSet(Collection<? extends E> c) {
map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
addAll(c);
} /**
* 构建一个指定初始容量和负载因子的HashSet
*
* @param initialCapacity 指定的初始容量
* @param loadFactor 指定的负载因子
* @throws IllegalArgumentException
* 如果传入的初始容量和负载因子的值不在正确的范围,就抛异常
*/
public HashSet(int initialCapacity, float loadFactor) {
map = new HashMap<>(initialCapacity, loadFactor);
} /**
* 构建一个指定初始容量的HashSet,负载因子是默认的0.75
*
* @param initialCapacity 指定的初始容量
* @throws IllegalArgumentException
* 如果传入的初始容量的值小于0,就抛异常
*/
public HashSet(int initialCapacity) {
map = new HashMap<>(initialCapacity);
} /**
* 这个构造函数是构建一个LinkedHashMap
* 它只能够被LinkedHashSet使用
* @param initialCapacity 指定的初始容量
* @param loadFactor 指定的负载因子
* @param dummy 可以忽略这个参数,无具体意义
* @throws IllegalArgumentException
* 如果传入的初始容量和负载因子的值不在正确的范围,就抛异常
*/
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<>(initialCapacity, loadFactor);
}

HashSet的add方法

    /**
* add方法,将一个元素添加到hashSet中
* 如果hashSet中没有这个元素,就添加成功
* 如果该元素在集合中已经存在了,就直接返回false
* 这里添加重复的元素不是去覆盖之前的元素,而是直接添加不成功
*
* @param e 将要添加到集合中的元素
* @return <tt>true</tt> 添加成功返回true
* element
*/
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}

由于hashSet集合中的元素是无序的,所以不能像List一样通过get(int index)获取下标然后去获取元素的方式,所以hashSet没有get方法。

HashSet的iterator方法

    /**
* 通过迭代器的方式返回该集合中的元素,元素的返回没有特定的顺序
*
* @return an Iterator over the elements in this set
* @see ConcurrentModificationException
*/
public Iterator<E> iterator() {
return map.keySet().iterator();
}

使用示例:

public static void main(String[] args) {
Set<String> set = new HashSet<>();
set.add("中国");
set.add("你好");
set.add("中国");
System.out.println(set);
Iterator<String> ite = set.iterator();
//判断下一个元素之后是否有值
while(ite.hasNext()){
System.out.println(ite.next());
}
}

返回结果:



可以看到,最后迭代出的元素,并没有按照一定的顺序返回,hashSet中的元素是无序的。

HashSet的size方法

size方法比较简单,就是直接返回HashSet集合中元素有多少个

   /**
* 返回集合中元素的个数
*
* @return 元素的个数
*/
public int size() {
return map.size();
}

HashSet的isEmpty方法

isEmpty就是用来判断HashSet是否是个空的集合

    /**
* 判断集合是否是空
* 如果该集合中没有元素,就返回true
* @return <tt>true</tt> if this set contains no elements
*/
public boolean isEmpty() {
return map.isEmpty();
}

HashSet的remove和clear方法

 /**
* 移除集合中的元素,要是集合中有该元素,直接移除,返回true,不包含返回false
*
* @param o 需要移除的元素
* @return <tt>true</tt> if the set contained the specified element
*/
public boolean remove(Object o) {
return map.remove(o)==PRESENT;
} /**
* 移除集合中的所有元素
* The set will be empty after this call returns.
*/
public void clear() {
map.clear();
}

CopyOnWriteArraySet源码解析

CopyOnWriteArraySet是基于于CopyOnWriteArrayList实现的,CopyOnWriteArrayList的源码请看list那篇文章,CopyOnWriteArraySet是线程安全的。

CopyOnWriteArraySet的构造函数

//用来存储元素
private final CopyOnWriteArrayList<E> al; /**
* 构建一个空的集合
*/
public CopyOnWriteArraySet() {
al = new CopyOnWriteArrayList<E>();
} /**
* 构建一个包含指定集合所有元素的集合
* collection.
*
* @param c 传入的集合
* @throws NullPointerException 集合为空,就抛异常
*/
public CopyOnWriteArraySet(Collection<? extends E> c) {
if (c.getClass() == CopyOnWriteArraySet.class) {
@SuppressWarnings("unchecked") CopyOnWriteArraySet<E> cc =
(CopyOnWriteArraySet<E>)c;
al = new CopyOnWriteArrayList<E>(cc.al);
}
else {
al = new CopyOnWriteArrayList<E>();
al.addAllAbsent(c);
}
}

CopyOnWriteArraySet的add方法

    /**
* 添加元素:
* 如果集合中没有该元素,添加到集合
* 如果集合中有该元素,离开
*
* @param e element to be added to this set
* @return {@code true} 添加成功就返回true
*/
public boolean add(E e) {
return al.addIfAbsent(e);
}

接下来看下CopyOnWriteArrayList中的addIfAbsent方法:

    /**
* 如果要加入的元素不存在,就添加到数组中
*
* @param e 需要添加的元素
* @return {@code true} 如果元素已经在数组中了,返回true
*/
public boolean addIfAbsent(E e) {
//获取数组
Object[] snapshot = getArray();
//这一步,是判断元素是否在数组中已经存在了
//如果已经存在了就返回false
//如果不存在就调用addIfAbsent添加元素
return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false :
addIfAbsent(e, snapshot);
} /**
* 如果元素不存在就添加到数组
* recent snapshot does not contain e.
*/
private boolean addIfAbsent(E e, Object[] snapshot) {
//加锁
final ReentrantLock lock = this.lock;
lock.lock();
try {
//获取当前的数组
Object[] current = getArray();
//获取当前数组的长度
int len = current.length;
//如果两次获取的数组不一致,说明数组被修改了
if (snapshot != current) {
//再次检查,刚刚获取到的元素是否被修改了
int common = Math.min(snapshot.length, len);
for (int i = 0; i < common; i++)
//这一步,是说明元素不在addIfAbsent这个方法里面获取的数组里
//而在当前的数组里面,说明当前获取的数组里面,已经存在该元素了,那么返回false
if (current[i] != snapshot[i] && eq(e, current[i]))
return false;
//判断元素是否在数组中已经存在了,若存在返回false
if (indexOf(e, current, common, len) >= 0)
return false;
}
//copy一个新的数组,比原先的数组长度多一位
Object[] newElements = Arrays.copyOf(current, len + 1);
//然后把元素添加到新数组的尾部
newElements[len] = e;
//设置数组的引用是加入元素之后的数组
setArray(newElements);
//添加成功,返回true
return true;
} finally {
//释放锁
lock.unlock();
}
} private static int indexOf(Object o, Object[] elements,
int index, int fence) {
//如果要加入的元素是null
if (o == null) {
for (int i = index; i < fence; i++)
//通过遍历查找之前的数组中是否有null的元素,有就直接返回下标
if (elements[i] == null)
return i;
//如果要加入的元素不是null
} else {
for (int i = index; i < fence; i++)
//遍历查找元素是否在数组中已经存在,有就直接返回下标
if (o.equals(elements[i]))
return i;
}
return -1;
}

通过上面的addIfAbsent方法,可以保证集合中的元素不重复

CopyOnWriteArraySet的remove方法

   /**
* 如果元素存在,就移除
* @param o 将要移除的元素
* @return {@code true} 移除成功后返回true
*/
public boolean remove(Object o) {
return al.remove(o);
}

接下来看下CopyOnWriteArrayList中的remove方法:

    /**
* 移除列表中的第一个匹配的元素
* 如果存在直接移除,如果不存在返回false
*
* @param o 需要移除的元素
* @return {@code true}
*/
public boolean remove(Object o) {
//获取数组
Object[] snapshot = getArray();
//判断数组中是否有该元素
//如果没有返回false,如果存在该元素,调用移除方法
int index = indexOf(o, snapshot, 0, snapshot.length);
return (index < 0) ? false : remove(o, snapshot, index);
} /**
* 移除列表中的元素
*/
private boolean remove(Object o, Object[] snapshot, int index) {
//加锁
final ReentrantLock lock = this.lock;
lock.lock();
try {
//再次获取下当前数组
Object[] current = getArray();
//获取当前数组的长度
int len = current.length;
//判断数组是否被修改了
if (snapshot != current) findIndex: {
//再次检查,刚刚获取到的元素是否被修改了
int prefix = Math.min(index, len);
for (int i = 0; i < prefix; i++) {
//这一步,是说明元素不在addIfAbsent这个方法里面获取的数组里,而在当前的数组里面
if (current[i] != snapshot[i] && eq(o, current[i])) {
index = i;
break findIndex;
}
}
//如果该元素的下标大于或者等于数组的长度,返回false
if (index >= len)
return false;
//如果当前数组中有该元素,直接退出
if (current[index] == o)
break findIndex;
//判断元素是否在current里面
index = indexOf(o, current, index, len);
//不在就返回false
if (index < 0)
return false;
}
//创建一个比当前数组少一位的数组
Object[] newElements = new Object[len - 1];
//然后通过arraycopy的方式
//先把移除元素之前的元素copy出来
//然后把移除元素之后的元素左移
System.arraycopy(current, 0, newElements, 0, index);
System.arraycopy(current, index + 1,
newElements, index,
len - index - 1);
//设置当前数组的引用是移除之后的数组
setArray(newElements);
return true;
} finally {
//释放锁
lock.unlock();
}
}

CopyOnWriteArraySet的其他方法基本上实现都是和上述差不多的方式,就不一一赘述了。

ConcurrentSkipListSet源码解析

ConcurrentSkipListSet是基于ConcurrentSkipListMap实现的,线程安全,查询快,接下来直接说下ConcurrentSkipListMap的实现思想。

ConcurrentSkipListMap跳表,是使用链表和索引实现的:



上述图片画了个简单的跳表,可能不够准确,大致的思路也差不多是这样的。

HashSet、CopyOnWriteArraySet、ConcurrentSkipListSet源码解析(JDK1.8)的更多相关文章

  1. Java 集合系列16之 HashSet详细介绍(源码解析)和使用示例

    概要 这一章,我们对HashSet进行学习.我们先对HashSet有个整体认识,然后再学习它的源码,最后再通过实例来学会使用HashSet.内容包括:第1部分 HashSet介绍第2部分 HashSe ...

  2. 【转】Java 集合系列16之 HashSet详细介绍(源码解析)和使用示例--不错

    原文网址:http://www.cnblogs.com/skywang12345/p/3311252.html 概要 这一章,我们对HashSet进行学习.我们先对HashSet有个整体认识,然后再学 ...

  3. 源码解析JDK1.8-HashMap链表成环的问题解决方案

    前言 上篇文章详解介绍了HashMap在JDK1.7版本中链表成环的原因,今天介绍下JDK1.8针对HashMap线程安全问题的解决方案. jdk1.8 扩容源码解析 public class Has ...

  4. ArrayList、CopyOnWriteArrayList源码解析(JDK1.8)

    本篇文章主要是学习后的知识记录,存在不足,或许不够深入,还请谅解. 目录 ArrayList源码解析 ArrayList中的变量 ArrayList构造函数 ArrayList中的add方法 Arra ...

  5. Java集合-ArrayList源码解析-JDK1.8

    ◆ ArrayList简介 ◆ ArrayList 是一个数组队列,相当于 动态数组.与Java中的数组相比,它的容量能动态增长.它继承于AbstractList,实现了List, RandomAcc ...

  6. Map集合类(一.hashMap源码解析jdk1.8)

    java集合笔记一 java集合笔记二 java集合笔记三 jdk 8 之前,其内部是由数组+链表来实现的,而 jdk 8 对于链表长度超过 8 的链表将转储为红黑树 1.属性 //节点数组,第一次使 ...

  7. java集合 源码解析 学习手册

    学习路线: http://www.cnblogs.com/skywang12345/ 总结 1 总体框架 2 Collection架构 3 ArrayList详细介绍(源码解析)和使用示例 4 fai ...

  8. 给jdk写注释系列之jdk1.6容器(6)-HashSet源码解析&Map迭代器

    今天的主角是HashSet,Set是什么东东,当然也是一种java容器了.      现在再看到Hash心底里有没有会心一笑呢,这里不再赘述hash的概念原理等一大堆东西了(不懂得需要先回去看下Has ...

  9. 给jdk写注释系列之jdk1.6容器(8)-TreeSet&NavigableMap&NavigableSet源码解析

    TreeSet是一个有序的Set集合. 既然是有序,那么它是靠什么来维持顺序的呢,回忆一下TreeMap中是怎么比较两个key大小的,是通过一个比较器Comparator对不对,不过遗憾的是,今天仍然 ...

随机推荐

  1. [C语言学习笔记三]格式化输出和输入

    使用 printf 函数来输出,使用 scanf 函数来输入 在 printf 函数中使用变量,需要使用占位符代替. int 型一般存储整数,使用 %d 代替 long long int 型一般存储长 ...

  2. Pandas初体验

    目录 Pandas 一.简介 1.安装 2.引用方法 二.series 1.创建方法 2.缺失数据处理 2.1 什么是缺失值 2.2 NaN特性 2.3 填充NaN 2.4 删除NaN 2.5 其他方 ...

  3. 【转+】以C++为核心语言的高频交易系统的讨论

    [前言]高频交易是量化交易的核心.主要分两个方向:计算机技术和交易策略.策略各有不同,一般都是数据分析的专家或者金融,机器学习从业者.在计算机技术方面,一个是交易平台的性能,二者是硬件的性能,延时的多 ...

  4. POJ-2516(最小费用最大流+MCMF算法)

    Minimum Cost POJ-2516 题意就是有n个商家,有m个供货商,然后有k种商品,题目求的是满足商家的最小花费供货方式. 对于每个种类的商品k,建立一个超级源点和一个超级汇点.每个商家和源 ...

  5. Gym100923H Por Costel and the Match

    题目链接:http://codeforces.com/gym/100923/problem/H 分析:并查集,用enemy储存x的敌人,用weight储存权重决定根节点 需用scanf和puts输入输 ...

  6. 2020年12月-第02阶段-前端基础-CSS Day02

    CSS Day02 复合选择器 后代选择器 并集选择器 1. CSS复合选择器 理解 理解css复合选择器分别的应用场景 为什么要学习css复合选择器 CSS选择器分为 基础选择器 和 复合选择器 , ...

  7. 内置了一个缓冲区(数组)缓冲流BufferInputStream为何要配合字节数组的使用?

    内置了一个缓冲区(数组)缓冲流BufferInputStream为何要配合字节数组的使用? 只为效率而积累[积少成多送一趟比送多趟快] 举例子:超市买30个鸡蛋回家煮 (1)读一个送一个(效率太低啦) ...

  8. 2019HDU多校第七场 HDU6646 A + B = C 【模拟】

    一.题目 A + B = C 二.分析 比较考验码力的题. 对于$c$,因为首位肯定不为0,那么$a$或者$b$至少有一个最高位是和$c$平齐的,或者少一位(相当于$a$+$b$进位得到). 那么这里 ...

  9. Java中的Set集合

    Set接口简介 Set接口和List接口一样,同样继承自Collection接口,它与Collection接口中的方法基本一致,并没有对Collection接口进行功能上的扩充,它是比Collecti ...

  10. 08、元组tuple

    元组(tuple) 是一个有序且不可变的容器,在里面可以存放多个不同类型的元素 元组是在最后多一个逗号,用于表示它是一个元组 tuple = (11,22,'阿斯顿','媚媚',) #后面多加一个逗号 ...