HashSet
字数: 0 字 时长: 0 分钟
1. HashSet 概述
HashSet 是 Java 中 Set 接口最常用的实现类之一。
它最大的特点就是:
元素不能重复并且:
不保证元素的遍历顺序HashSet 的底层实际上是:
HashMap可以简单理解为:
HashSet
↓
HashMap
↓
Set 中的元素作为 HashMap 的 Key而 HashMap 的:
Key 不能重复所以 HashSet 自然而然也就实现了:
元素不能重复HashSet 的类定义:
public class HashSet<E>
extends AbstractSet<E>
implements Set<E>,
Cloneable,
Serializable {
}继承关系:
Collection
↑
Set
↑
HashSet2. HashSet 基本使用
来看一个简单的例子:
Set<String> set = new HashSet<>();
set.add("张无忌");
set.add("赵敏");
set.add("周芷若");
set.add("张无忌");
System.out.println(set);虽然:
"张无忌"添加了两次,但最终 HashSet 中只会保存一份。
因为:
HashSet
↓
不允许重复元素需要注意:
HashSet 不保证元素的遍历顺序。
所以不要依赖:
插入顺序来判断 HashSet 的输出顺序。
3. HashSet 的底层结构
HashSet 中有一个非常重要的字段:
private transient HashMap<E, Object> map;也就是说:
HashSet
↓
内部维护一个 HashMapHashSet 中的元素:
E会被保存为 HashMap 的:
Key那 HashMap 的 Value 放什么?
HashSet 定义了一个固定对象:
private static final Object PRESENT =
new Object();所以整个结构可以理解为:
HashSet 元素
↓
HashMap Key
PRESENT
↓
HashMap Value例如:
set.add("张无忌");底层实际上类似于:
map.put(
"张无忌",
PRESENT
);因此:
HashSet本质上可以理解为:
只使用 HashMap 的 Key,而不关心 Value。
4. 构造方法
4.1 无参构造
创建 HashSet:
HashSet<String> set =
new HashSet<>();底层:
public HashSet() {
map =
new HashMap<>();
}也就是说:
new HashSet()
↓
new HashMap()4.2 指定初始容量
也可以指定初始容量:
HashSet<String> set =
new HashSet<>(16);对应源码:
public HashSet(
int initialCapacity) {
map =
new HashMap<>(
initialCapacity
);
}本质仍然是把参数传给:
HashMap4.3 指定初始容量和负载因子
HashSet<String> set =
new HashSet<>(
16,
0.75f
);源码:
public HashSet(
int initialCapacity,
float loadFactor) {
map =
new HashMap<>(
initialCapacity,
loadFactor
);
}所以 HashSet 的:
容量
负载因子
扩容本质上都依赖底层:
HashMap5. add()
HashSet 中最重要的方法就是:
add()源码非常简单:
public boolean add(
E e) {
return map.put(
e,
PRESENT
) == null;
}可以看到:
HashSet.add(e)
↓
HashMap.put(e, PRESENT)也就是说:
HashSet 元素
↓
HashMap Key而:
PRESENT只是一个固定的占位对象。
5.1 为什么不能添加重复元素
假设:
set.add("张无忌");底层:
map.put(
"张无忌",
PRESENT
);第一次添加:
HashMap 中不存在这个 Key
↓
插入成功再次添加:
set.add("张无忌");还是:
map.put(
"张无忌",
PRESENT
);因为 HashMap 中:
Key 唯一所以不会再创建一个新的 "张无忌"。
而是:
找到原来的 Key
↓
更新 Value但由于 Value 本身始终都是:
PRESENT所以从 HashSet 的角度来看:
什么都没有发生这就是 HashSet:
不能保存重复元素的根本原因。
6. HashSet 如何判断元素是否重复
既然 HashSet 底层使用的是:
HashMap那么判断元素是否重复,本质上也就是:
HashMap 判断 Key 是否相同的过程。
整个过程可以简单理解为:
元素
↓
hashCode()
↓
计算 hash
↓
计算数组下标
↓
找到对应桶
↓
比较 Key最终还会结合:
hashCode()
equals()判断元素是否相同。
所以对于自定义对象,如果希望 HashSet 正确判断重复元素,就需要正确处理:
hashCode()和:
equals()例如:
class User {
private String name;
private int age;
}如果希望:
name 和 age 相同的两个 User 被认为是同一个元素,就应该按照这个规则重写:
equals()
hashCode()否则即使两个对象中的数据完全一样:
new User("张无忌", 20)
new User("张无忌", 20)HashSet 也可能把它们当成两个不同对象。
所以:
HashSet 的去重不是简单依靠 hashCode,而是依赖底层 HashMap 对 Key 的判断逻辑。
7. contains()
判断 HashSet 中是否存在某个元素:
set.contains(
"张无忌"
);源码:
public boolean contains(
Object o) {
return map.containsKey(o);
}可以看到:
HashSet.contains()
↓
HashMap.containsKey()因为 HashSet 中的元素本来就是:
HashMap 的 Key所以只需要判断:
Key 是否存在即可。
8. remove()
删除元素:
set.remove(
"张无忌"
);源码:
public boolean remove(
Object o) {
return map.remove(o)
== PRESENT;
}本质:
HashSet.remove()
↓
HashMap.remove()
↓
根据 Key 删除键值对如果删除成功:
返回 PRESENT那么:
map.remove(o) == PRESENT结果就是:
true9. iterator()
HashSet 也可以使用迭代器进行遍历:
Iterator<String> iterator =
set.iterator();
while (
iterator.hasNext()
) {
System.out.println(
iterator.next()
);
}HashSet 的 iterator():
public Iterator<E> iterator() {
return map
.keySet()
.iterator();
}可以看到:
HashSet.iterator()
↓
HashMap.keySet()
↓
iterator()还是在遍历:
HashMap 的 Key这再次说明:
HashSet 中的元素
=
HashMap 中的 Key10. null 元素
HashSet 可以保存:
null例如:
Set<String> set =
new HashSet<>();
set.add(null);
set.add(null);
System.out.println(set);最终只会保留一个:
null原因还是因为底层 HashMap:
允许一个 null Key所以:
HashSet也允许保存:
一个 null 元素重复添加:
null仍然不会产生多个元素。
11. 常用方法对应关系
HashSet 的很多核心操作实际上都是直接调用 HashMap:
| HashSet | HashMap |
|---|---|
add(e) | put(e, PRESENT) |
contains(o) | containsKey(o) |
remove(o) | remove(o) |
iterator() | keySet().iterator() |
size() | size() |
isEmpty() | isEmpty() |
clear() | clear() |
整体关系:
HashSet
↓
HashMap
元素
↓
Key
Value
↓
PRESENT12. 小结
HashSet 是 Set 接口最常用的实现之一。
它最重要的特点:
元素不能重复
不保证遍历顺序
允许一个 null底层实现:
HashSet
↓
HashMap其中:
HashSet 元素
↓
HashMap Key
HashMap Value
↓
固定的 PRESENT 对象所以 HashSet 的:
添加
删除
查找
去重本质上都依赖:
HashMap核心源码关系:
add()
↓
map.put(e, PRESENT)
contains()
↓
map.containsKey(o)
remove()
↓
map.remove(o)
iterator()
↓
map.keySet().iterator()因此理解 HashSet 最重要的一句话就是:
HashSet 本质上是利用 HashMap 的 Key 唯一性,实现了元素不可重复的 Set。