Set 接口
Set,数学意义上的集合,指的是结构数据唯一、数据存储无序、没有跟索引相关的方法,不能用普通的for循环遍历,必须使用迭代器遍历或者增强for循环遍历的数据结构。
public interface Set<E> extends Collection<E> {
// 集合中添加元素
boolean add(E e);
// 移除集合中的元素
boolean remove(Object o);
// 清空集合
void clear();
// 是否包含某个元素
boolean contains(Object o);
// 是否为空
boolean isEmpty();
// 返回遍历集合的迭代器
Iterator<E> iterator();
// 集合中元素个数
int size();
}
Set 接口实现类
HashSet
使用 HashMap 作为底层数据结构存放数据,HashSet 中将要存放的数据作为 HashMap 的 Key,HashMap 中的 value 为一个在 HashSet 中定义的所有 value 共享的 Object 对象。
存放数据的过程需要先计算 hash 值,在根据 hash 值和一个表达式计算元素在数组中存放的位置,这和HashMap 存放数据的过程是一致的。保存在 HashSet 中的对象一定要重写 hashcode 和 equals 方法。
- 类的声明和重要的字段
public class HashSet<E> extends AbstractSet<E>
implements Set<E>, Cloneable, java.io.Serializable {
// HashMap 保存数据
private transient HashMap<E,Object> map;
// HashMap 中 Entry 共享的 value 对象
private static final Object PRESENT = new Object();
// ---------------------构造方法------------------------
public HashSet() {
// 初始化容量为16,负载因子为0.75
map = new HashMap<>();
}
public HashSet(int initialCapacity, float loadFactor) {
// 指定初始化容量和负载因子
map = new HashMap<>(initialCapacity, loadFactor);
}
// 使用 LinkedHashMap 作为实现类
// dummy 仅仅只是来作为和其他构造方法的重载区别,没有其他意义 - 官方说明
// 因此可以看成有 dummy 参数的就是使用 LinkedHashMap 作为底层的数据结构存储数据
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
}
- 常用操作
// 添加操作
public boolean add(E e) {
// HashMap第一次添加的时候返回null,如果是返回null则证明是添加成功的操作
return map.put(e, PRESENT)==null;
}
// 移除操作,
public boolean remove(Object o) {
return map.remove(o)==PRESENT;
}
// HashSet 中没有获取元素的操作,只能通过迭代的方式来遍历数据
public Iterator<E> iterator() {
return map.keySet().iterator();
}
HashSet 中的操作基本都是通过 HashMap 来完成的。
TreeSet
使用 TreeSet 可以保存数据唯一、并且按照升序进行排序,对 TreeSet 进行遍历也是按照排序后的顺序进行遍历的。由于其需要对数据进行排序,因此对于自定义的引用类型需要实现内部比较器或者外部比较器。否则会在编译阶段就抛出错误。
底层使用两种不同类型的数据结构作为存储结构
- 无参构造或者比较器构造,底层使用 TreeMap,使用共享的 PRESENT 作为 value 值。
- 传入 NavigableMap 对象,底层使用 NavigableMap。
- 传入 SortedSet 对象,底层使用 TreeMap。
- 类的定义和关键字段
public class TreeSet<E> extends AbstractSet<E>
implements NavigableSet<E>, Cloneable, java.io.Serializable
{
/**
* 可导航的 Map 结构
*/
private transient NavigableMap<E,Object> m;
// 共享的 value 结构
private static final Object PRESENT = new Object();
/**
* 基于可导航 map 的构造方法
*/
TreeSet(NavigableMap<E,Object> m) {
this.m = m;
}
public TreeSet() {
this(new TreeMap<E,Object>());
}
// 对于自定义的引用类型,使用 TreeMap 时要么实现内部比较器
// 要么传入外部比较器
public TreeSet(Comparator<? super E> comparator) {
this(new TreeMap<>(comparator));
}
// 仍然使用 TreeMap 数据结构作为底层的数据存储结构
public TreeSet(SortedSet<E> s) {
this(s.comparator());
addAll(s);
}
}
- 添加元素操作
public boolean add(E e) {
return m.put(e, PRESENT)==null;
}
往集合中添加一个元素,添加成功返回 true,元素已经存在了返回 false。
- 移除操作
// 对象存在返回 true,对象不存在返回 false
public boolean remove(Object o) {
return m.remove(o)==PRESENT;
}
/**
* 清空 set 集合
*/
public void clear() {
m.clear();
}
- 其他方法
// 返回迭代器
public Iterator<E> iterator() {
return m.navigableKeySet().iterator();
}
// 集合大小
public int size() {
return m.size();
}
// 集合是否为空
public boolean isEmpty() {
return m.isEmpty();
}
// 集合是否包含某个元素
public boolean contains(Object o) {
return m.containsKey(o);
}
LinkedHashSet
JDK 1.4 引入,和 HashSet 一个主要的区别就是存储的元素可以按照输入顺序进行输出。其实现是在 HashSet 的基础上多了一个总链表,将放入的元素串在一起,方便有序的遍历。
- 类的声明及关键字段
public class LinkedHashSet<E>
extends HashSet<E>
implements Set<E>, Cloneable, java.io.Serializable {
public LinkedHashSet(int initialCapacity, float loadFactor) {
super(initialCapacity, loadFactor, true);
}
public LinkedHashSet(int initialCapacity) {
// 传入 true 就是让 HashSet 底层使用 LinkedHashMap
super(initialCapacity, .75f, true);
}
// 默认的负载因子为 0.75
public LinkedHashSet() {
super(16, .75f, true);
}
}
底层是使用 HashSet,LinkedHashSet 的实现并不是基于 HashSet 的包装,而是直接继承自 HashSet。同时注意到构造方法的第三个参数都是有传递参数,因此更进一步的是使用 LinkedHashMap 作为底层数据的存储结构。
由于是继承自 HashSet,相关的增删查改操作都在 HashSet 中定义了,因此源码也比较短。
总结
- Set 一般用来保存唯一的数据,Set 集合中的元素本身的遍历是无序的。
- 为了能够实现对集合本身有序遍历、排序遍历,因此分别在 Set 的基础上实现 LinkedHashSet、TreeSet。
- Set 结构的底层都是基于 Map 结构来实现的,要保存到 Set 中的数据结构就是作为 Map 的 key 来进行保存的,同时其 value 是共享的一个 Object 对象。