java学习--LinkedHashSet

一、LinkedHashSet 是什么?

LinkedHashSet 是 Java 集合框架中 java.util 包下的实现类,它继承自 HashSet ,同时实现了 Set 接口,底层基于 LinkedHashMap 实现(本质是「哈希表 + 双向链表」)。

可以把它理解为:

  • 拥有 HashSet 的高效查询、去重特性(基于哈希表);
  • 额外通过双向链表 维护元素的插入顺序,解决了 HashSet 无序的问题。

核心特点:

  • 有序:能精准保留元素的插入顺序(遍历顺序 = 插入顺序),但不支持按索引访问;
  • 不可重复 :和 HashSet 一样,通过 hashCode() + equals() 保证去重;
  • 允许 null 值:仅能有一个 null(因为不可重复);
  • 非线程安全 :多线程环境需用 Collections.synchronizedSet(new LinkedHashSet<>()) 包装;
  • 效率 :查询 / 增删效率略低于 HashSet(多了链表维护的开销),但远高于 TreeSet,理想时间复杂度仍为 O(1)

二、核心原理

LinkedHashSet 的底层结构是「哈希表(数组 + 链表 / 红黑树) + 双向链表」:

  1. 哈希表:负责保证元素不重复、高效查询(和 HashSet 逻辑一致);
  2. 双向链表:额外记录元素的插入顺序,遍历的时候按链表顺序输出,而非哈希表的随机顺序。

简单来说: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)
核心优势 有序 + 高效去重 极致高效去重 排序 + 去重
适用场景 需保留插入顺序的去重 无需有序的高效去重 需排序的去重

总结

  1. LinkedHashSet 继承自 HashSet,底层是「哈希表 + 双向链表」,核心特性是保留插入顺序、不可重复、高效查询
  2. 它的去重逻辑和 HashSet 完全一致,自定义类作为元素时必须重写 hashCode()equals()
  3. 选择建议:无需有序用 HashSet,需保留插入顺序用 LinkedHashSet,需排序用 TreeSet。
相关推荐
这个图像胖嘟嘟2 小时前
前端开发的基本运行环境配置
开发语言·javascript·vue.js·react.js·typescript·npm·node.js
阿湯哥2 小时前
Spring AI Alibaba 实现 Workflow 全指南
java·人工智能·spring
星竹晨L2 小时前
【C++内存安全管理】智能指针的使用和原理
开发语言·c++
宵时待雨2 小时前
数据结构(初阶)笔记归纳3:顺序表的应用
c语言·开发语言·数据结构·笔记·算法
旺仔小拳头..2 小时前
Java ---变量、常量、类型转换、默认值、重载、标识符、输入输出、访问修饰符、泛型、迭代器
java·开发语言·python
副露のmagic2 小时前
更弱智的算法学习 day36
学习·算法
12344523 小时前
【面试复盘】有了equals为什么还要hashcode
java·后端
lsx2024063 小时前
Vue3 自定义指令
开发语言
牛奔3 小时前
Go语言中结构体转Map优雅实现
开发语言·后端·macos·golang·xcode