LinkedHashSet 是 Java 集合框架中 HashSet 的子类,它继承了 HashSet 的所有特性(如不允许重复元素、允许 null 值),并额外保证了元素的迭代顺序与插入顺序一致。其底层通过 LinkedHashMap 实现,兼顾了哈希表的高效性能和链表的顺序性。
有序性:与 HashSet 的无序性不同,LinkedHashSet 会记录元素的插入顺序,迭代时按元素添加的先后顺序返回。这种顺序性是通过底层链表结构维护的。
去重性:与 HashSet 一致,依赖元素的 hashCode() 和 equals() 方法判断重复,确保集合中无重复元素。
允许 null 值:最多可存储 1 个 null 值(符合去重规则)。
线程不安全:非线程安全集合,多线程并发修改可能导致 ConcurrentModificationException异常。
性能:基本操作(add、remove、contains)的时间复杂度与 HashSet 接近(理想情况下 O (1)),但由于需要维护链表结构,性能略低于 HashSet。迭代操作的性能优于 HashSet,因为无需遍历整个哈希表,只需遍历链表(时间复杂度与元素数量成正比 O (n),但常数项更小)。
下面是一个 LinkedHashSet 的简单示例,展示了其初始化、添加元素、删除元素、遍历等常用操作:
package com.hxstrive.java_collection.linkedHashSet; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.Set; public class LinkedHashSetDemo { public static void main(String[] args) { // 1. 初始化 LinkedHashSet(无参构造) Set<String> linkedHashSet = new LinkedHashSet<>(); // 2. 添加元素(add() 方法) // 返回值为 boolean:添加成功返回 true,重复元素添加失败返回 false boolean isAdded1 = linkedHashSet.add("张三"); boolean isAdded2 = linkedHashSet.add("李四"); boolean isAdded3 = linkedHashSet.add("王五"); boolean isAdded4 = linkedHashSet.add("张三"); // 添加重复元素 System.out.println("添加结果:" + isAdded1 + "," + isAdded2 + "," + isAdded3 + "," + isAdded4); System.out.println("添加后集合元素(保持插入顺序):" + linkedHashSet); // 3. 判断元素是否存在(contains() 方法) boolean has1 = linkedHashSet.contains("李四"); boolean has2 = linkedHashSet.contains("赵六"); System.out.println("\n是否包含李四:" + has1); System.out.println("是否包含赵六:" + has2); // 4. 删除元素(remove() 方法) boolean isRemoved = linkedHashSet.remove("李四"); System.out.println("\n删除李四的结果:" + isRemoved); System.out.println("删除后集合元素:" + linkedHashSet); // 5. 重新添加已删除的元素,观察顺序变化 linkedHashSet.add("李四"); System.out.println("\n重新添加李四后:" + linkedHashSet); // 新元素会放在末尾 // 6. 集合大小(size() 方法) System.out.println("\n集合当前大小:" + linkedHashSet.size()); // 7. 遍历 LinkedHashSet(所有方式均保持插入顺序) // 方式1:增强 for 循环 System.out.println("\n增强 for 循环遍历:"); for (String name : linkedHashSet) { System.out.print(name + " "); } // 方式2:迭代器(Iterator) System.out.println("\n\n迭代器遍历:"); Iterator<String> iterator = linkedHashSet.iterator(); while (iterator.hasNext()) { String name = iterator.next(); System.out.print(name + " "); } // 方式3:forEach + Lambda 表达式(Java 8+) System.out.println("\n\nLambda 表达式遍历:"); linkedHashSet.forEach(name -> System.out.print(name + " ")); // 8. 清空集合(clear() 方法) linkedHashSet.clear(); System.out.println("\n\n清空集合后是否为空:" + linkedHashSet.isEmpty()); } }
运行结果:
添加结果:true,true,true,false 添加后集合元素(保持插入顺序):[张三, 李四, 王五] 是否包含李四:true 是否包含赵六:false 删除李四的结果:true 删除后集合元素:[张三, 王五] 重新添加李四后:[张三, 王五, 李四] 集合当前大小:3 增强 for 循环遍历: 张三 王五 李四 迭代器遍历: 张三 王五 李四 Lambda 表达式遍历: 张三 王五 李四 清空集合后是否为空:true Process finished with exit code 0
LinkedHashSet 继承自 HashSet,其核心是通过调用 HashSet 的一个特殊构造方法,将底层存储结构指定为 LinkedHashMap(而非 HashMap),下面是 HashSet 的一个特殊构造方法源码:
/** * 构造一个新的、空的链接哈希集合。(这个包访问权限的构造方法仅被 LinkedHashSet 使用) * 底层的 HashMap 实例实际上是一个具有指定初始容量和指定负载因子的 LinkedHashMap。 * * 注:此构造方法是 HashSet 为子类 LinkedHashSet 专门提供的, * 用于创建基于 LinkedHashMap的 集合实例,从而实现元素的插入顺序保持特性。 * * @param initialCapacity 哈希映射的初始容量 * @param loadFactor 哈希映射的负载因子 * @param dummy 忽略此参数(仅用于区分此构造方法与其他具有int、float参数的构造方法) * @throws IllegalArgumentException 如果初始容量小于0,或者负载因子为非正数(≤0) */ HashSet(int initialCapacity, float loadFactor, boolean dummy) { // 初始化底层存储为 LinkedHashMap,而非默认的 HashMap // 这是 LinkedHashSet 能够保持插入顺序的核心原因 map = new LinkedHashMap<>(initialCapacity, loadFactor); }
LinkedHashSet 的源码比较简单,提供了四个构造方法和一个 spliterator() 方法,构造方法如下:
public class LinkedHashSet<E> extends HashSet<E> implements Set<E>, Cloneable, java.io.Serializable { @java.io.Serial private static final long serialVersionUID = -2851667679971038690L; /** * 构造一个新的、空的 LinkedHashSet,指定初始容量和负载因子 * 底层通过 LinkedHashMap 实现,保证元素的插入顺序 * * @param initialCapacity LinkedHashSet 的初始容量 * @param loadFactor LinkedHashSet 的负载因子 * @throws IllegalArgumentException 如果初始容量小于0,或负载因子为非正数(≤0) */ public LinkedHashSet(int initialCapacity, float loadFactor) { super(initialCapacity, loadFactor, true); } /** * 构造一个新的、空的 LinkedHashSet,指定初始容量,使用默认负载因子(0.75) * * @param initialCapacity LinkedHashSet 的初始容量 * @throws IllegalArgumentException 如果初始容量小于0 */ public LinkedHashSet(int initialCapacity) { super(initialCapacity, .75f, true); } /** * 构造一个新的、空的LinkedHashSet,使用默认初始容量(16)和默认负载因子(0.75) */ public LinkedHashSet() { super(16, .75f, true); } /** * 构造一个新的 LinkedHashSet,包含指定集合中的所有元素 * 初始容量设置为足以容纳指定集合元素的大小,使用默认负载因子(0.75) * * @param c 包含要放入此集合的元素的集合 * @throws NullPointerException 如果指定的集合为null */ public LinkedHashSet(Collection<? extends E> c) { // 计算初始容量:取"2*集合大小"和11中的较大值,确保有足够空间存储元素 // 调用父类构造方法,指定容量、默认负载因子0.75和LinkedHashMap标识 super(Math.max(2*c.size(), 11), .75f, true); addAll(c); } // 省略 spliterator 方法 }
spliterator() 方法用来创建 Spliterator 的实例,Spliterator(可分割迭代器)是 Java 8 引入的一个新接口,位于 java.util 包下,主要用于支持集合的并行遍历,是对传统 Iterator 的补充。它允许将集合分割成多个子部分进行并行处理,从而充分利用多核处理器的性能优势。
spliterator() 方法定义如下:
/** * 创建一个延迟绑定且快速失败的 Spliterator,用于遍历集合中的元素 * Spliterator 支持并行遍历,是 Iterator 的补充。 * * 此 Spliterator 会报告以下特性: * (1)Spliterator.SIZED:已知大小(可通过size()获取) * (2)Spliterator.DISTINCT:元素不重复(符合Set特性) * (3)Spliterator.ORDERED:元素按插入顺序排列(是 LinkedHashSet 的核心特性) * * 此实现通过集合的迭代器(Iterator)来创建延迟绑定的拆分迭代器。 * 该拆分迭代器继承了集合迭代器的快速失败属性。 * 创建的拆分迭代器还会额外报告 Spliterator.SUBSIZED 特性。 * * @return 用于遍历集合元素的 Spliterator * @since 1.8 */ @Override public Spliterator<E> spliterator() { // 创建 Spliterator,指定集合和特性(元素不重复 + 有序) return Spliterators.spliterator(this, Spliterator.DISTINCT | Spliterator.ORDERED); }
更多信息请参考 https://docs.oracle.com/javase/8/docs/api/java/util/LinkedHashSet.html API 文档。