文章目录
本系列文章:
Java集合(一)集合框架概述
Java集合(二)ArrayList、LinkedList使用及源码分析
Java集合(三)Vector、Stack使用及源码分析
Java集合(四)HashMap、Hashtable、LinkedHashMap使用及源码分析
Java集合(五)HashSet、LinkedHashSet使用及源码分析
Java集合(六)ArrayBlockingQueue、LinkedBlockingQueue使用及源码分析
HashSet
一、HashSet概述
- 1、HashSet的继承关系
简单来说,HashSet 是一个无序集合。HashSet的继承关系:
public class HashSet<E>
extends AbstractSet<E>
implements Set<E>, Cloneable, java.io.Serializable
图示:
从继承关系上来看,HashSet的特性:
- 继承了AbstractSet,实现了Set,可以进行Set相关操作。
- 间接实现了Cloneable接口,能被克隆。
- 间接实现了Serializable接口,支持序列化。
- 2、HashSet的底层结构
HashSet的底层结构是HashMap,简单来说,HashSet是value是固定值(Object PRESENT = new Object()
)的HashMap。
二、HashSet特点
2.1 HashSet的特点
- 1、HashSet的
底层实现是HashMap
(HashSet的值存放于HashMap的key上,HashMap的value是一个统一的值)。 - 2、HashSet中的
元素无序且不能重复
(从插入HashSet元素的顺序和遍历HashSet的顺序对比可以看出)。 - 3、HashSet是
线程不安全
的。如果要保证线程安全,其中一种方法是将其改造成线程安全的类,示例:
Set set = Collections.synchronizedSet(new HashSet(...));
- 4、HashSet
允许存入null
。
2.2 HashSet保证数据不可重复的方式
向HashSet 中add元素时,判断元素是否存在的依据,不仅要比较hash值,同时还要结合equles 方法比较
。
HashMap 的 key 是唯一的,由源码可以看出 HashSet 添加进去的值就是作为HashMap 的key,并且在HashMap中如果K/V相同时,会用新的V覆盖掉旧的V,然后返回旧的V,所以不会重复(HashMap 比较key是否相等是先比较hashcode 再比较equals
)。
hashCode()与equals()的相关规定:
- 如果两个对象相等,则hashcode一定也是相同的;
- 两个对象相等,对两个equals方法返回true;
- 两个对象有相同的hashcode值,它们也不一定是相等的;
- 综上,equals方法被重写,则hashCode方法也必须被重写;
- hashCode()的默认行为是对堆上的对象产生独特值。如果没有重写hashCode(),则该class的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)。
三、HashSet使用
3.1 常用方法介绍
- 1、构造一个新的空HashSet,默认初始容量为16,负载因子为0.75
public HashSet()
- 2、构造一个新的空HashSet,指定初始容量,负载因子为0.75
public HashSet(int initialCapacity)
- 3、构造一个新的空HashSet,指定初始容量和负载因子
public HashSet(int initialCapacity, float loadFactor)
- 4、添加元素
public boolean add(E e)
- 5、清空集合
public void clear()
- 6、如果此集合包含指定的元素,则返回 true
public boolean contains(Object o)
- 7、如果此集合不包含任何元素,则返回 true
public boolean isEmpty()
- 8、获取此集合中元素的迭代器
public Iterator<E> iterator()
- 9、删除元素
public boolean remove(Object o)
- 10、返回此集合中的元素数
public int size()
2.2 常用方法使用
//1.构造方法
HashSet<String> hashSet = new HashSet<>();
HashSet<String> hashSet2 = new HashSet<>(20);
HashSet<String> hashSet3 = new HashSet<>(20,0.8f);
//2.添加元素
hashSet.add("abc");
System.out.println(hashSet); //[abc]
//3.删除所有元素
hashSet.clear();
System.out.println(hashSet); //[]
//4.判断是否包含指定的元素
System.out.println(hashSet.contains("1")); //false
//5.判断是否为空
System.out.println(hashSet.isEmpty()); //true
//6.获取迭代器
hashSet.add("123");
hashSet.add("456");
hashSet.add("789");
hashSet.add("101");
Iterator<String> iterator = hashSet.iterator();
while(iterator.hasNext()) {
System.out.print(iterator.next()+" "); //123 101 456 789
}
System.out.println();
//7.删除元素
hashSet.remove("789");
hashSet.forEach(x -> System.out.print(x+" ")); //123 101 456
System.out.println();
//8.返回此集合中的元素数
System.out.println(hashSet.size()); //3
三、HashSet源码
HashSet源码中最先出现的是定义了几个变量:
//HashSet集合中的内容是通过HashMap数据结构来存储的
private transient HashMap<E,Object> map;
//向HashSet中添加数据,数据在上面的map结构是作为key存在的,而value统一都是
//PRESENT,这样做是因为Set只使用到了HashMap的key,所以此处定义一个静态的常
//量Object类,来充当HashMap的value
private static final Object PRESENT = new Object();
3.1 构造方法
- 1、HashSet()
//直接 new 一个HashMap对象出来,采用无参的 HashMap 构造函数,
//HashMap对象具有默认初始容量(16)和加载因子(0.75)
public HashSet() {
map = new HashMap<>();
}
- 2、HashSet(int initialCapacity, float loadFactor)
//指定初始容量和加载因子,创建HashMap实例
public HashSet(int initialCapacity, float loadFactor) {
map = new HashMap<>(initialCapacity, loadFactor);
}
- 3、HashSet(int initialCapacity)
//指定初始容量,创建HashMap
public HashSet(int initialCapacity) {
map = new HashMap<>(initialCapacity);
}
- 4、HashSet(int initialCapacity, float loadFactor, boolean dummy)
//以指定的initialCapacity和loadFactor构造一个新的空链接哈希集合。
//此构造函数为包访问权限,不对外公开,实际只是对LinkedHashSet的支持。
//实际底层会以指定的参数构造一个空LinkedHashMap实例来实现。
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
加载因子指的是能存储的元素占总的容量的比例。在 HashMap 中,能够存储元素的数量就是:总的容量*加载因子
。
每当向HashSet新增元素时,如果HashMap集合中的元素大于前面公式计算的结果,就必须要进行扩容操作,从时间和空间考虑,加载因子一般都选默认的0.75。
3.2 添加元素
当把对象加入HashSet时,HashSet会先计算对象的 hashcode 值来判断对象加入的位置,同时也会与其他加入的对象的hashcode值作比较,如果没有相符的 hashcode,HashSet 会假设对象没有重复出现。但是如果发现有相同 hashcode 值的对象,这时会调用 equals()方法来检查 hashcode 相等的对象是否真的相同。如果两者相同,则覆盖旧元素。
//将 e 作为 key,PRESENT 作为 value 插入到 map 集合中,如果 e
//不存在,则插入成功返回 true;如果存在,则返回false
//本质上是调用HashMap的put方法来实现的
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
3.3 删除元素
//删除成功返回 true,删除的元素不存在会返回 false
//本质上是调用HashMap的remove方法来实现的
public boolean remove(Object o) {
return map.remove(o)==PRESENT;
}
3.4 查找元素
//调用 HashMap 的 containsKey(Object o) 方法,找到了返回 true,找不到返回 false
//因为HashSet的本质上是用HashMap来存储元素的,HashSet的值是HashMap中的key,所以
//此处调用了HashMap的containsKey方法来判断
public boolean contains(Object o) {
return map.containsKey(o);
}
3.5 清空集合/判断是否为空/获取HashSet元素个数
这几个方法都是直接调用其底层实现HashMap的方法的,源码:
//清空集合
public void clear() {
map.clear();
}
//判断是否为空
public boolean isEmpty() {
return map.isEmpty();
}
//获取集合元素个数
public int size() {
return map.size();
}
3.6 迭代器
//因为HashSet的本质上是用HashMap来存储元素的,HashSet的值是HashMap中的key,所以
//此处调用了HashMap的keySet方法来遍历HashSet中的元素
public Iterator<E> iterator() {
return map.keySet().iterator();
}
LinkedHashSet
一、LinkedHashSet概述
- 1、LinkedHashSet的继承关系
LinkedHashSet是有序集合,其继承关系:
public class LinkedHashSet<E>
extends HashSet<E>
implements Set<E>, Cloneable, java.io.Serializable
图示:
从继承关系上来看,LinkedHashSet的特性:
- 继承HashSet,实现Set接口,可以进行Set相关的操作。
- 实现了Cloneable接口,能被克隆。
- 实现了Serializable接口,这意味着Serializable支持序列化,能通过序列化去传输。
- 2、LinkedHashSet的底层结构
LinkedHashSet底层是通过LinkedHashMap来实现的,LinkedHashMap其实也就是value是固定值的LinkedHashMap。
二、LinkedHashSet特点
2.1 LinkedHashSet的特点
- 底层是用LinkedHashMap来实现的。
- 线程不安全 。
- 元素有序,是按照插入的顺序排序的。
- 最多只能存一个null。
2.2 LinkedHashSet支持按元素访问顺序排序吗
LinkedHashSet所有的构造方法都是调用HashSet的同一个构造方法:
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
然后,通过调用LinkedHashMap的构造方法初始化Map:
public LinkedHashMap(int initialCapacity, float loadFactor) {
super(initialCapacity, loadFactor);
accessOrder = false;
}
可以看出:这里把accessOrder写死为false,所以,LinkedHashSet是不支持按访问顺序对元素排序的,只能按插入顺序排序。
三、LinkedHashSet使用
3.1 常用方法介绍
- 1、构造一个具有默认初始容量(16)和负载因子(0.75)的LinkedHashSet
public LinkedHashSet()
- 2、构造一个具有指定初始容量和默认负载因子(0.75)的LinkedHashSet
public HashSet(int initialCapacity)
- 3、构造具有指定的初始容量和负载因子的LinkedHashSet
public HashSet(int initialCapacity, float loadFactor)
3.2 方法使用
//1.构造方法
LinkedHashSet<String> linkedHashSet = new LinkedHashSet<>();
LinkedHashSet<String> linkedHashSet2 = new LinkedHashSet<>(20);
LinkedHashSet<String> linkedHashSet3 = new LinkedHashSet<>(20,0.7f);
//2.获取元素迭代器
linkedHashSet.add("1");
linkedHashSet.add("2");
linkedHashSet.add("3");
Iterator<String> iterator = linkedHashSet.iterator();
while (iterator.hasNext()) {
System.out.print(iterator.next()+" "); //1 2 3
}
System.out.println();
//3.获取元素个数
System.out.println(linkedHashSet.size()); //3
//4.是否为空
System.out.println(linkedHashSet.isEmpty()); //false
//5.是否包含某个元素
System.out.println(linkedHashSet.contains("2")); //true
//6.删除某个元素
linkedHashSet.remove("1");
linkedHashSet.forEach(x -> System.out.print(x+" ")); //2 3
System.out.println();
//7.清空集合
linkedHashSet.clear();
System.out.println(linkedHashSet); //[]
四、LinkedHashSet源码
LinkedHashSet源码很简单。
- 1、LinkedHashSet(int initialCapacity, float loadFactor)
public LinkedHashSet(int initialCapacity, float loadFactor) {
//调用其父类HashSet的构造器,指定初始容量和增长因子,构造一个LinkedHashMap
super(initialCapacity, loadFactor, true);
}
- 2、LinkedHashSet(int initialCapacity)
public LinkedHashSet(int initialCapacity) {
//调用其父类HashSet的构造器,指定初始容量,增长因子为0.75,构造一个LinkedHashMap
super(initialCapacity, .75f, true);
}
- 3、public LinkedHashSet()
public LinkedHashSet() {
//调用其父类HashSet的构造器,初始容量为16,增长因子为0.75,构造一个LinkedHashMap
super(16, .75f, true);
}