day10_HashSet与其他集合
·
day10:HashSet与其他集合
📚 学习目标
- 理解HashSet的实现原理
- 掌握HashSet的常用方法
- 理解TreeSet与自然排序
- 理解LinkedHashSet的特点
- 掌握各集合类的选择和使用
- 理解Collections工具类的使用
一、HashSet概述
1.1 HashSet简介
什么是HashSet?
HashSet是基于HashMap实现的Set集合,存储不重复的元素。
继承体系
java.lang.Object
└── java.util.AbstractCollection<E>
└── java.util.AbstractSet<E>
└── java.util.HashSet<E>
实现接口
- Set:集合接口
- Cloneable:支持克隆
- Serializable:支持序列化
1.2 HashSet特点
| 特点 | 说明 |
|---|---|
| 无序性 | 不保证元素的顺序 |
| 元素唯一 | 不允许存储重复元素 |
| 允许null | 允许存储一个null元素 |
| 非线程安全 | 多线程环境下需要手动同步 |
| 底层实现 | 基于HashMap |
| 查询快 | 基于哈希表,contains时间复杂度O(1) |
二、HashSet源码分析
2.1 核心成员变量
public class HashSet<E>
extends AbstractSet<E>
implements Set<E>, Cloneable, java.io.Serializable {
// 底层使用HashMap存储元素
private transient HashMap<E,Object> map;
// 空的Object对象,作为HashMap的value
private static final Object PRESENT = new Object();
}
2.2 构造方法
public class HashSet<E> {
// 底层创建一个空的HashMap
public HashSet() {
map = new HashMap<>();
}
// 指定初始容量
public HashSet(int initialCapacity) {
map = new HashMap<>(initialCapacity);
}
// 指定初始容量和负载因子
public HashSet(int initialCapacity, float loadFactor) {
map = new HashMap<>(initialCapacity, loadFactor);
}
// 使用集合初始化
public HashSet(Collection<? extends E> c) {
map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
addAll(c);
}
}
2.3 常用方法实现
// add方法
public boolean add(E e) {
return map.put(e, PRESENT) == null;
}
// remove方法
public boolean remove(Object o) {
return map.remove(o) == PRESENT;
}
// contains方法
public boolean contains(Object o) {
return map.containsKey(o);
}
// size方法
public int size() {
return map.size();
}
// isEmpty方法
public boolean isEmpty() {
return map.isEmpty();
}
// clear方法
public void clear() {
map.clear();
}
三、HashSet常用方法
3.1 添加元素
import java.util.HashSet;
public class HashSetAddDemo {
public static void main(String[] args) {
HashSet<String> set = new HashSet<>();
// 1. add(E e):添加元素
boolean added1 = set.add("Java");
boolean added2 = set.add("Python");
boolean added3 = set.add("Java"); // 重复元素
System.out.println("添加Java: " + added1); // true
System.out.println("添加Python: " + added2); // true
System.out.println("添加重复Java: " + added3); // false
System.out.println("HashSet: " + set);
// 2. addAll(Collection c):添加集合
HashSet<String> otherSet = new HashSet<>();
otherSet.add("C++");
otherSet.add("Go");
set.addAll(otherSet);
System.out.println("addAll后: " + set);
}
}
3.2 删除元素
import java.util.HashSet;
public class HashSetRemoveDemo {
public static void main(String[] args) {
HashSet<String> set = new HashSet<>();
set.add("Java");
set.add("Python");
set.add("C++");
set.add("Go");
System.out.println("原始集合: " + set);
// 1. remove(Object o):删除元素
boolean removed = set.remove("Python");
System.out.println("删除Python: " + removed); // true
System.out.println("删除后: " + set);
// 2. removeAll(Collection c):删除集合中的所有元素
HashSet<String> toRemove = new HashSet<>();
toRemove.add("C++");
toRemove.add("Ruby");
boolean changed = set.removeAll(toRemove);
System.out.println("removeAll: " + changed);
System.out.println("removeAll后: " + set);
// 3. clear():清空集合
set.clear();
System.out.println("clear后: " + set);
System.out.println("isEmpty: " + set.isEmpty());
}
}
3.3 查询元素
import java.util.HashSet;
public class HashSetQueryDemo {
public static void main(String[] args) {
HashSet<String> set = new HashSet<>();
set.add("Java");
set.add("Python");
set.add("C++");
set.add("Go");
// 1. contains(Object o):判断是否包含元素
System.out.println("contains(\"Java\"): " + set.contains("Java"));
System.out.println("contains(\"Rust\"): " + set.contains("Rust"));
// 2. isEmpty():判断是否为空
System.out.println("isEmpty: " + set.isEmpty());
// 3. size():获取元素个数
System.out.println("size: " + set.size());
// 4. 遍历
System.out.println("\n遍历方式1:forEach");
for (String item : set) {
System.out.println(item);
}
System.out.println("\n遍历方式2:Iterator");
java.util.Iterator<String> iterator = set.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}
四、HashSet去重原理
4.1 去重机制
HashSet通过hashCode()和equals()两个方法判断元素是否重复。
去重流程
1. 添加元素A
↓
2. 计算A的hashCode()
↓
3. 根据hashCode()找到存储位置
↓
4. 如果该位置为空,直接存入
↓
5. 如果该位置不为空,遍历链表/红黑树
↓
6. 比较A与已有元素的equals()
↓
7. 如果equals()返回true,拒绝添加
↓
8. 否则,添加成功
4.2 自定义对象去重
import java.util.HashSet;
import java.util.Objects;
public class HashSetDeduplication {
public static void main(String[] args) {
HashSet<Student> set = new HashSet<>();
set.add(new Student(1, "张三"));
set.add(new Student(2, "李四"));
set.add(new Student(1, "张三")); // 会去重吗?
System.out.println("集合大小: " + set.size()); // 取决于是否重写hashCode和equals
for (Student s : set) {
System.out.println(s);
}
}
}
class Student {
private int id;
private String name;
public Student(int id, String name) {
this.id = id;
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return id == student.id && Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(id, name);
}
@Override
public String toString() {
return "Student{id=" + id + ", name='" + name + "'}";
}
}
五、TreeSet
5.1 TreeSet概述
什么是TreeSet?
TreeSet是基于红黑树实现的Set集合,可以对元素进行排序。
特点
| 特点 | 说明 |
|---|---|
| 有序 | 按照自然顺序或自定义顺序排序 |
| 元素唯一 | 不允许存储重复元素 |
| 非线程安全 | 多线程环境下需要手动同步 |
| 底层实现 | 基于TreeMap(红黑树) |
| 不允许null | 不允许存储null(除非Comparator允许) |
5.2 自然排序
import java.util.TreeSet;
public class TreeSetNaturalOrder {
public static void main(String[] args) {
TreeSet<Integer> set = new TreeSet<>();
// 添加元素
set.add(5);
set.add(2);
set.add(8);
set.add(1);
set.add(9);
System.out.println("TreeSet: " + set);
System.out.println("first: " + set.first());
System.out.println("last: " + set.last());
System.out.println("lower(5): " + set.lower(5));
System.out.println("higher(5): " + set.higher(5));
// 遍历(有序)
System.out.println("\n遍历:");
for (int num : set) {
System.out.println(num);
}
}
}
5.3 自定义排序
import java.util.TreeSet;
public class TreeSetCustomOrder {
public static void main(String[] args) {
// 方式1:使用Lambda表达式(Java 8+)
TreeSet<String> set1 = new TreeSet<>((s1, s2) -> s2.compareTo(s1));
set1.add("Apple");
set1.add("Banana");
set1.add("Cherry");
System.out.println("降序: " + set1);
// 方式2:使用Comparator接口
TreeSet<String> set2 = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
set2.add("apple");
set2.add("BANANA");
set2.add("Cherry");
System.out.println("忽略大小写: " + set2);
// 方式3:自定义Comparator
TreeSet<Student> studentSet = new TreeSet<>(new StudentComparator());
studentSet.add(new Student(3, "张三"));
studentSet.add(new Student(1, "李四"));
studentSet.add(new Student(2, "王五"));
System.out.println("\n学生按年龄排序:");
for (Student s : studentSet) {
System.out.println(s);
}
}
}
// 学生类
class Student {
private int age;
private String name;
public Student(int age, String name) {
this.age = age;
this.name = name;
}
public int getAge() { return age; }
public String getName() { return name; }
@Override
public String toString() {
return "Student{age=" + age + ", name='" + name + "'}";
}
}
// 自定义比较器
import java.util.Comparator;
class StudentComparator implements Comparator<Student> {
@Override
public int compare(Student s1, Student s2) {
return s1.getAge() - s2.getAge();
}
}
六、LinkedHashSet
6.1 LinkedHashSet概述
什么是LinkedHashSet?
LinkedHashSet是HashSet的子类,基于LinkedHashMap实现,可以维护元素的插入顺序。
特点
| 特点 | 说明 |
|---|---|
| 有序 | 保持元素的插入顺序 |
| 元素唯一 | 不允许存储重复元素 |
| 允许null | 允许存储一个null元素 |
| 底层实现 | 基于LinkedHashMap |
6.2 使用示例
import java.util.LinkedHashSet;
public class LinkedHashSetDemo {
public static void main(String[] args) {
LinkedHashSet<String> set = new LinkedHashSet<>();
set.add("Java");
set.add("Python");
set.add("C++");
set.add("Java"); // 重复,不会添加
set.add("Go");
System.out.println("LinkedHashSet: " + set);
System.out.println("保持插入顺序: " + set);
// LinkedHashSet vs HashSet
System.out.println("\n对比HashSet:");
java.util.HashSet<String> hashSet = new java.util.HashSet<>();
hashSet.add("Java");
hashSet.add("Python");
hashSet.add("C++");
hashSet.add("Go");
System.out.println("HashSet: " + hashSet); // 无序
}
}
七、各Set集合对比
7.1 对比表
| 对比项 | HashSet | TreeSet | LinkedHashSet |
|---|---|---|---|
| 底层实现 | HashMap | TreeMap | LinkedHashMap |
| 顺序 | 无序 | 有序(自然/自定义) | 插入顺序 |
| 查找效率 | O(1) | O(log n) | O(1) |
| 插入删除效率 | O(1) | O(log n) | O(1) |
| null支持 | 允许一个null | 不允许null | 允许一个null |
| 线程安全 | 否 | 否 | 否 |
7.2 选择建议
| 场景 | 推荐使用 |
|---|---|
| 只关心元素唯一,不关心顺序 | HashSet |
| 需要对元素排序 | TreeSet |
| 需要保持插入顺序 | LinkedHashSet |
| 需要频繁查询 | HashSet |
| 需要范围操作 | TreeSet |
八、Collections工具类
8.1 排序操作
import java.util.*;
public class CollectionsSortDemo {
public static void main(String[] args) {
// 1. sort(List<T> list):自然排序
List<Integer> list1 = new ArrayList<>();
list1.add(5);
list1.add(2);
list1.add(8);
list1.add(1);
Collections.sort(list1);
System.out.println("sort: " + list1);
// 2. sort(List<T> list, Comparator<? super T> c):自定义排序
List<String> list2 = new ArrayList<>();
list2.add("Banana");
list2.add("Apple");
list2.add("Cherry");
Collections.sort(list2, Collections.reverseOrder());
System.out.println("降序: " + list2);
// 3. reverse(List<?> list):反转
Collections.reverse(list1);
System.out.println("reverse: " + list1);
// 4. shuffle(List<?> list):随机打乱
Collections.shuffle(list1);
System.out.println("shuffle: " + list1);
// 5. swap(List<?> list, int i, int j):交换
Collections.swap(list1, 0, list1.size() - 1);
System.out.println("swap: " + list1);
}
}
8.2 查找操作
import java.util.*;
public class CollectionsSearchDemo {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1, 3, 5, 7, 9, 11);
// 1. binarySearch(List<?> list, T key):二分查找(必须先排序)
int index = Collections.binarySearch(list, 7);
System.out.println("binarySearch(7): " + index);
// 2. max(Collection<?> coll):获取最大元素
System.out.println("max: " + Collections.max(list));
// 3. min(Collection<?> coll):获取最小元素
System.out.println("min: " + Collections.min(list));
// 4. frequency(Collection<?> c, Object o):统计出现次数
List<String> list2 = Arrays.asList("A", "B", "A", "C", "A");
System.out.println("frequency(A): " + Collections.frequency(list2, "A"));
}
}
8.3 同步控制
import java.util.*;
public class CollectionsSyncDemo {
public static void main(String[] args) {
// 1. synchronizedList:创建线程安全的List
List<String> syncList = Collections.synchronizedList(new ArrayList<>());
// 2. synchronizedSet:创建线程安全的Set
Set<String> syncSet = Collections.synchronizedSet(new HashSet<>());
// 3. synchronizedMap:创建线程安全的Map
Map<String, Integer> syncMap = Collections.synchronizedMap(new HashMap<>());
// 注意:迭代时需要加锁
synchronized (syncList) {
for (String s : syncList) {
System.out.println(s);
}
}
}
}
8.4 不可变集合
import java.util.*;
public class CollectionsImmutableDemo {
public static void main(String[] args) {
// 1. emptyList():空List
List<String> emptyList = Collections.emptyList();
// emptyList.add("test"); // UnsupportedOperationException
// 2. singletonList(T o):单元素List
List<String> singletonList = Collections.singletonList("唯一");
System.out.println("singletonList: " + singletonList);
// 3. unmodifiableList(List<? extends T> list):不可变List
List<String> modifiable = new ArrayList<>();
modifiable.add("A");
List<String> unmodifiable = Collections.unmodifiableList(modifiable);
// unmodifiable.add("B"); // UnsupportedOperationException
// 4. Map的不可变集合
Map<String, Integer> singletonMap = Collections.singletonMap("key", 1);
Map<String, Integer> unmodifiableMap = Collections.unmodifiableMap(new HashMap<>());
}
}
九、实践案例
9.1 数组去重
import java.util.*;
public class ArrayDeduplication {
public static void main(String[] args) {
// 方法1:使用HashSet去重
Integer[] arr = {1, 2, 3, 2, 4, 3, 5, 1, 6, 5};
Set<Integer> set = new HashSet<>(Arrays.asList(arr));
System.out.println("去重后: " + set);
// 方法2:保持顺序的去重
LinkedHashSet<Integer> linkedSet = new LinkedHashSet<>(Arrays.asList(arr));
System.out.println("保持顺序去重: " + linkedSet);
// 方法3:统计重复元素
Map<Integer, Integer> countMap = new HashMap<>();
for (Integer num : arr) {
countMap.merge(num, 1, Integer::sum);
}
System.out.println("元素统计: " + countMap);
}
}
9.2 找出两个数组的交集和并集
import java.util.*;
public class SetOperations {
public static void main(String[] args) {
Set<Integer> set1 = new HashSet<>(Arrays.asList(1, 2, 3, 4, 5));
Set<Integer> set2 = new HashSet<>(Arrays.asList(4, 5, 6, 7, 8));
// 并集
Set<Integer> union = new HashSet<>(set1);
union.addAll(set2);
System.out.println("并集: " + union);
// 交集
Set<Integer> intersection = new HashSet<>(set1);
intersection.retainAll(set2);
System.out.println("交集: " + intersection);
// 差集(set1 - set2)
Set<Integer> difference = new HashSet<>(set1);
difference.removeAll(set2);
System.out.println("差集(set1-set2): " + difference);
// 对称差集(set1 ∪ set2 - set1 ∩ set2)
Set<Integer> symmetricDifference = new HashSet<>(union);
symmetricDifference.removeAll(intersection);
System.out.println("对称差集: " + symmetricDifference);
}
}
9.3 成绩统计系统
import java.util.*;
public class ScoreStatistics {
public static void main(String[] args) {
// 准备数据
List<Integer> scores = Arrays.asList(
85, 92, 78, 95, 88, 76, 91, 83, 90, 72,
88, 95, 81, 77, 89, 94, 86, 79, 92, 85
);
// 使用TreeSet进行统计
TreeSet<Integer> scoreSet = new TreeSet<>(scores);
System.out.println("========== 成绩统计 ==========");
System.out.println("学生人数: " + scores.size());
System.out.println("最高分: " + scoreSet.last());
System.out.println("最低分: " + scoreSet.first());
System.out.println("平均分: " +
String.format("%.2f", scores.stream()
.mapToInt(Integer::intValue).average().orElse(0)));
// 分数段统计
System.out.println("\n分数段统计:");
System.out.println("90-100分: " + scoreSet.subSet(90, 101).size() + "人");
System.out.println("80-89分: " + scoreSet.subSet(80, 90).size() + "人");
System.out.println("70-79分: " + scoreSet.subSet(70, 80).size() + "人");
System.out.println("60-69分: " + scoreSet.subSet(60, 70).size() + "人");
System.out.println("60分以下: " + scoreSet.headSet(60).size() + "人");
// 及格率
long passCount = scores.stream().filter(s -> s >= 60).count();
System.out.println("\n及格率: " +
String.format("%.2f%%", (double) passCount / scores.size() * 100));
}
}
十、常见问题与注意事项
10.1 HashSet vs HashMap
public class HashSetVsHashMap {
public static void main(String[] args) {
// HashSet内部使用HashMap存储
// key: 元素值
// value: 固定的PRESENT对象
HashSet<String> set = new HashSet<>();
set.add("Java");
set.add("Python");
// 实际上等价于
HashMap<String, Object> map = new HashMap<>();
map.put("Java", new Object());
map.put("Python", new Object());
// 不能像Map一样使用get方法获取元素
// set.get("Java"); // 错误,没有这个方法
// 应该使用contains方法
System.out.println("contains(\"Java\"): " + set.contains("Java"));
}
}
10.2 TreeSet元素要求
public class TreeSetRequirement {
public static void main(String[] args) {
TreeSet<Student> set = new TreeSet<>();
// 错误:Student类没有实现Comparable接口
// set.add(new Student(1, "张三"));
// 解决方案1:让Student实现Comparable接口
// 解决方案2:创建TreeSet时传入Comparator
TreeSet<Student> set2 = new TreeSet<>((s1, s2) -> s1.getAge() - s2.getAge());
set2.add(new Student2(20, "张三"));
set2.add(new Student2(18, "李四"));
System.out.println(set2);
}
}
class Student2 {
private int age;
private String name;
public Student2(int age, String name) {
this.age = age;
this.name = name;
}
public int getAge() { return age; }
@Override
public String toString() {
return "Student{age=" + age + ", name='" + name + "'}";
}
}
十一、课后作业
必做题
-
数组去重练习
- 使用HashSet对数组去重
- 使用LinkedHashSet保持顺序去重
- 统计重复元素及其次数
-
Set集合操作
- 实现交集、并集、差集
- 实现对称差集
-
TreeSet排序练习
- 创建自定义对象TreeSet
- 实现自然排序和自定义排序
选做题
-
通讯录去重
- 使用HashSet对联系人去重
- 保持插入顺序
-
成绩排名系统
- 使用TreeSet实现成绩排名
- 支持查询前N名、后N名
十二、学习总结
今日要点
-
HashSet
- 基于HashMap实现
- 元素唯一,无序
- 通过hashCode和equals去重
-
TreeSet
- 基于TreeMap实现(红黑树)
- 元素唯一,有序
- 支持自然排序和自定义排序
-
LinkedHashSet
- 基于LinkedHashMap实现
- 元素唯一,保持插入顺序
-
Collections工具类
- 排序、查找、同步控制、不可变集合
选择建议
- 需要唯一性 → HashSet
- 需要排序 → TreeSet
- 需要顺序 → LinkedHashSet
下一课预告:泛型机制
学习时间:建议2-3小时(理论1小时 + 实践1-2小时)
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)