一、LinkedHashSet 是什么?
LinkedHashSet 是 Java 集合框架中 java.util 包下的实现类,它继承自 HashSet ,同时实现了 Set 接口,底层基于 LinkedHashMap 实现(本质是「哈希表 + 双向链表」)。
可以把它理解为:
- 拥有 HashSet 的高效查询、去重特性(基于哈希表);
- 额外通过双向链表 维护元素的插入顺序,解决了 HashSet 无序的问题。
核心特点:
- 有序:能精准保留元素的插入顺序(遍历顺序 = 插入顺序),但不支持按索引访问;
- 不可重复 :和 HashSet 一样,通过
hashCode()+equals()保证去重; - 允许 null 值:仅能有一个 null(因为不可重复);
- 非线程安全 :多线程环境需用
Collections.synchronizedSet(new LinkedHashSet<>())包装; - 效率 :查询 / 增删效率略低于 HashSet(多了链表维护的开销),但远高于 TreeSet,理想时间复杂度仍为
O(1)。
二、核心原理
LinkedHashSet 的底层结构是「哈希表(数组 + 链表 / 红黑树) + 双向链表」:
- 哈希表:负责保证元素不重复、高效查询(和 HashSet 逻辑一致);
- 双向链表:额外记录元素的插入顺序,遍历的时候按链表顺序输出,而非哈希表的随机顺序。
简单来说:LinkedHashSet 就是给 HashSet 加了一条 "记录插入顺序" 的双向链表,既保留了 HashSet 的高效,又解决了无序的问题。
三、常用操作示例
下面是 LinkedHashSet 的完整使用示例,代码可直接运行,对比 HashSet 能明显看出 "有序" 的特性:
import java.util.LinkedHashSet;
import java.util.Iterator;
public class LinkedHashSetDemo {
public static void main(String[] args) {
// 1. 创建 LinkedHashSet 对象
LinkedHashSet<String> lhs = new LinkedHashSet<>();
// 2. 添加元素(重复元素不会被插入,且保留插入顺序)
lhs.add("西瓜");
lhs.add("苹果");
lhs.add("香蕉");
lhs.add("苹果"); // 重复元素,不插入
lhs.add(null); // 允许一个 null
System.out.println("初始集合:" + lhs);
// 输出:[西瓜, 苹果, 香蕉, null](严格按插入顺序,而非哈希随机顺序)
// 3. 判断元素是否存在
boolean hasBanana = lhs.contains("香蕉");
System.out.println("是否包含香蕉:" + hasBanana); // true
// 4. 删除元素(删除后链表顺序仍保持)
lhs.remove(null);
lhs.remove("西瓜");
System.out.println("删除后集合:" + lhs);
// 输出:[苹果, 香蕉](剩余元素仍按原插入顺序)
// 5. 遍历(三种方式,均按插入顺序输出)
// 方式1:增强 for 循环(最常用)
System.out.println("增强for循环遍历:");
for (String fruit : lhs) {
System.out.println(fruit); // 苹果 → 香蕉
}
// 方式2:迭代器
System.out.println("迭代器遍历:");
Iterator<String> iterator = lhs.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next()); // 苹果 → 香蕉
}
// 方式3:forEach 方法(Java 8+)
System.out.println("forEach遍历:");
lhs.forEach(System.out::println); // 苹果 → 香蕉
// 6. 清空集合
lhs.clear();
System.out.println("清空后是否为空:" + lhs.isEmpty()); // true
// 7. 自定义类作为元素(需重写 hashCode + equals,和 HashSet 一致)
LinkedHashSet<Book> bookSet = new LinkedHashSet<>();
bookSet.add(new Book(1, "Java编程思想"));
bookSet.add(new Book(2, "Effective Java"));
bookSet.add(new Book(1, "Java编程思想")); // 重复,不插入
System.out.println("书籍集合:" + bookSet.size()); // 2
}
// 自定义书籍类(重写 hashCode 和 equals 保证去重)
static class Book {
private int id;
private String name;
public Book(int id, String name) {
this.id = id;
this.name = name;
}
// 重写 hashCode:根据 id 和 name 生成
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + id;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
// 重写 equals:id 和 name 相同则为同一本书
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Book other = (Book) obj;
return id == other.id && (name == null ? other.name == null : name.equals(other.name));
}
// 重写 toString,方便打印
@Override
public String toString() {
return "Book{id=" + id + ", name='" + name + "'}";
}
}
}
四、LinkedHashSet vs HashSet vs TreeSet(核心对比)
为了帮你理清三者的选择逻辑,这里整理了关键区别:
| 特性 | LinkedHashSet | HashSet | TreeSet |
|---|---|---|---|
| 底层结构 | 哈希表 + 双向链表 | 哈希表 | 红黑树 |
| 有序性 | 插入顺序 | 无序 | 自然顺序 / 自定义排序 |
| 去重规则 | hashCode() + equals() | hashCode() + equals() | Comparable/Comparator |
| 允许 null | 是(仅一个) | 是(仅一个) | 否 |
| 时间复杂度 | O (1)(略高于 HashSet) | O (1)(最优) | O(log n) |
| 核心优势 | 有序 + 高效去重 | 极致高效去重 | 排序 + 去重 |
| 适用场景 | 需保留插入顺序的去重 | 无需有序的高效去重 | 需排序的去重 |
总结
- LinkedHashSet 继承自 HashSet,底层是「哈希表 + 双向链表」,核心特性是保留插入顺序、不可重复、高效查询。
- 它的去重逻辑和 HashSet 完全一致,自定义类作为元素时必须重写
hashCode()和equals()。 - 选择建议:无需有序用 HashSet,需保留插入顺序用 LinkedHashSet,需排序用 TreeSet。
