滚雪球学Java(64):LinkedHashSet原理及实现解析

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介: 【6月更文挑战第18天】🏆本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!

在这里插入图片描述

  咦咦咦,各位小可爱,我是你们的好伙伴——bug菌,今天又来给大家普及Java SE相关知识点了,别躲起来啊,听我讲干货还不快点赞,赞多了我就有动力讲得更嗨啦!所以呀,养成先点赞后阅读的好习惯,别被干货淹没了哦~

在这里插入图片描述


🏆本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,助你一臂之力,带你早日登顶🚀,欢迎大家关注&&收藏!持续更新中,up!up!up!!

环境说明:Windows 10 + IntelliJ IDEA 2021.3.2 + Jdk 1.8

@[toc]

前言

  在Java开发中,使用集合类是必不可少的。其中,HashSet是最常用的集合类之一,但是HashSet在保证元素唯一性的同时,却不能保证插入顺序。因此,我们需要LinkedHashSet,在HashSet的基础上保证了元素插入的顺序。本文将对LinkedHashSet进行原理及实现的解析,帮助读者更好地理解LinkedHashSet。

摘要

  LinkedHashSet是HashSet和LinkedHashMap的结合体,它具有HashSet的高效查找和LinkedHashMap的有序性。LinkedHashSet底层使用的是LinkedHashMap实现,数据结构是一个双链表和一个哈希表。本文将深入剖析LinkedHashSet的实现原理和应用场景,帮助读者更好地理解该类的使用。

LinkedHashSet

概述

  LinkedHashSet是Java中的一个集合类,它实现了Set接口,继承了HashSet类,具有HashSet类的高效存储和检索元素的特点。同时,LinkedHashSet还对元素的插入顺序进行了维护,保证了元素的有序性,这是HashSet所不具备的。在实现上,LinkedHashSet底层使用的是LinkedHashMap,使用双向链表维护了元素的插入顺序。LinkedHashSet提供了以下构造方法:

public LinkedHashSet()
public LinkedHashSet(int initialCapacity)
public LinkedHashSet(int initialCapacity, float loadFactor)
public LinkedHashSet(Collection<? extends E> c)

  其中,第一个构造方法创建一个空的LinkedHashSet实例,第二个构造方法指定了初始化容量,第三个构造方法指定了初始化容量和加载因子,第四个构造方法使用指定集合来初始化LinkedHashSet。

源代码解析

  LinkedHashSet底层使用的是LinkedHashMap,它是一个基于散列表的Map实现。它的数据结构是一个双链表和一个哈希表。双链表用来维护元素的插入顺序,哈希表用来实现高效的存储和查找。

LinkedHashSet类的源代码如下:

public class LinkedHashSet<E>
    extends HashSet<E>
    implements Set<E>, Cloneable, java.io.Serializable
{
   
   
    private static final long serialVersionUID = -2851667679971038690L;

    private final LinkedHashMap<E,Object> map;

    // Dummy value to associate with an Object in the backing Map
    private static final Object PRESENT = new Object();

    public LinkedHashSet() {
   
   
        map = new LinkedHashMap<>();
    }

    public LinkedHashSet(int initialCapacity) {
   
   
        super(initialCapacity);
        map = new LinkedHashMap<>(initialCapacity);
    }

    public LinkedHashSet(int initialCapacity, float loadFactor) {
   
   
        super(initialCapacity, loadFactor);
        map = new LinkedHashMap<>(initialCapacity, loadFactor);
    }

    public LinkedHashSet(Collection<? extends E> c) {
   
   
        map = new LinkedHashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
        addAll(c);
    }

    public Iterator<E> iterator() {
   
   
        return map.keySet().iterator();
    }

    public int size() {
   
   
        return map.size();
    }

    public boolean isEmpty() {
   
   
        return map.isEmpty();
    }

    public boolean contains(Object o) {
   
   
        return map.containsKey(o);
    }

    public boolean add(E e) {
   
   
        return map.put(e, PRESENT)==null;
    }

    public boolean remove(Object o) {
   
   
        return map.remove(o)==PRESENT;
    }

    public void clear() {
   
   
        map.clear();
    }

    public Object clone() {
   
   
        LinkedHashSet<E> clone = null;
        try {
   
   
            clone = (LinkedHashSet<E>) super.clone();
        } catch (CloneNotSupportedException e) {
   
   
            throw new InternalError();
        }
        clone.map = (LinkedHashMap<E,Object>) map.clone();
        return clone;
    }

    private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException {
   
   
        s.defaultWriteObject();
        s.writeInt(map.capacity());
        s.writeFloat(map.loadFactor());
        s.writeInt(map.size());
        for (E e : map.keySet())
            s.writeObject(e);
    }

    private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
   
   
        s.defaultReadObject();
        int capacity = s.readInt();
        float loadFactor = s.readFloat();
        int size = s.readInt();
        map = new LinkedHashMap<>(capacity, loadFactor);
        for (int i=0; i<size; i++) {
   
   
            E e = (E) s.readObject();
            map.put(e, PRESENT);
        }
    }
}

  可以看到,在LinkedHashSet的构造方法中,它使用LinkedHashMap来实现对元素插入顺序的维护。LinkedHashMap维护了一个双向链表,每次插入元素时,它会将新元素插入到链表的尾部,同时在哈希表中存储该元素的键值对,以便实现高效的存储和查找。LinkedHashSet中的add()方法就是通过调用LinkedHashMap中的put()方法实现插入新元素,而contains()方法则是直接调用LinkedHashMap的containsKey()方法实现对元素是否存在的判断。

  如下是部分源码截图:

在这里插入图片描述

  具体源码分析如下:
  LinkedHashSet是一个继承自HashSet的类,实现了Set接口,支持元素按照插入顺序进行遍历。它内部通过使用一个LinkedHashMap来维护元素插入顺序,实际上LinkedHashSet就是在HashSet的基础上利用了LinkedHashMap的有序性。

  LinkedHashSet提供了多个构造方法,可以仅传入初始容量、初始容量和负载因子等参数,也可以传入一个集合类对象进行初始化。

  LinkedHashSet的主要方法和HashSet相同,例如:contains(),add(),remove()等。因为继承自HashSet,所以LinkedHashSet也是基于哈希表实现的。其迭代器返回元素的顺序与元素插入顺序一致。

  LinkedHashSet的clone方法会创建一个新的LinkedHashSet对象,并将自身的LinkedHashMap进行浅复制。

  LinkedHashSet还实现了Serializable接口,在序列化和反序列化时,会将其内部的LinkedHashMap也一并进行序列化和反序列化。

在这里插入图片描述

应用场景案例

  LinkedHashSet在保证元素唯一性的同时,还保留了元素的插入顺序,因此,它在以下场景中得到了广泛应用:

  1. 缓存:当需要缓存一组数据时,如果希望缓存的数据按照插入顺序进行访问,就可以使用LinkedHashSet。

  2. 配置文件:当需要加载一组配置文件,并按照文件的出现顺序来访问它们的内容时,可以使用LinkedHashSet。

  3. 去重:当需要对一组数据进行去重,同时还需要保留它们的插入顺序时,可以使用LinkedHashSet。

优缺点分析

  LinkedHashSet的优点在于它继承了HashSet的高效性和LinkedHashMap的有序性,同时支持高效的元素查找、插入和删除等操作。其缺点在于它需要维护一个双向链表,因此,在内存使用方面可能会略微占用一些空间。

类代码方法介绍

  下面是LinkedHashSet类中的主要方法介绍:

  1. add(E e):向集合中添加元素,并返回添加成功与否的结果。

  2. remove(Object o):从集合中删除指定的元素,并返回删除结果。

  3. contains(Object o):判断集合中是否包含指定元素,如果包含则返回true,否则返回false。

  4. iterator():返回一个迭代器,用于遍历集合中的元素。

  5. size():返回集合中元素的数量。

  6. isEmpty():判断集合是否为空,如果为空则返回true,否则返回false。

测试用例

下面是使用LinkedHashSet的一个简单测试用例:

测试代码演示

package com.demo.javase.day64;

import java.util.LinkedHashSet;

/**
 * @Author bug菌
 * @Date 2023-11-06 11:36
 */
public class LinkedHashSetTest {
   
   

    public static void main(String[] args) {
   
   
        LinkedHashSet<String> linkedHashSet = new LinkedHashSet<>();

        // 向集合中添加元素
        linkedHashSet.add("A");
        linkedHashSet.add("B");
        linkedHashSet.add("C");
        linkedHashSet.add("D");
        linkedHashSet.add("E");

        // 遍历集合中的元素,并输出它们的顺序
        for (String str : linkedHashSet) {
   
   
            System.out.print(str + " ");
        }
        System.out.println();

        // 删除集合中的元素
        linkedHashSet.remove("C");

        // 再次遍历集合中的元素,并输出它们的顺序
        for (String str : linkedHashSet) {
   
   
            System.out.print(str + " ");
        }
        System.out.println();

        // 判断集合中是否包含指定元素
        System.out.println("集合中是否包含\"A\":" + linkedHashSet.contains("A"));
        System.out.println("集合中是否包含\"C\":" + linkedHashSet.contains("C"));
    }
}

测试结果

  根据如上测试用例,本地测试结果如下,仅供参考,你们也可以自行修改测试用例或者添加更多的测试数据或测试方法,进行熟练学习以此加深理解。

输出结果如下:

A B C D E 
A B D E 
集合中是否包含"A"true
集合中是否包含"C"false

在这里插入图片描述

测试代码分析

  根据如上测试用例,在此我给大家进行深入详细的解读一下测试代码,以便于更多的同学能够理解并加深印象。

  该代码演示了如何使用Java中的LinkedHashSet类。LinkedHashSet是HashSet的一个子类,它保留了元素插入的顺序。具体实现过程如下:

  1. 创建一个LinkedHashSet实例,该实例用于存储字符串类型的元素。

  2. 使用add方法向集合中添加元素。

  3. 使用for-each循环遍历集合中的元素,并输出它们的顺序。

  4. 使用remove方法删除集合中的元素。

  5. 再次使用for-each循环遍历集合中的元素,并输出它们的顺序。

  6. 使用contains方法判断集合中是否包含指定元素,并输出判断结果。

在实际开发中,LinkedHashSet通常用于需要保留元素插入顺序的场景,例如需要记录日志的应用程序等。由于LinkedHashSet实现了Set接口,因此它也具有Set接口的特性,即不能包含重复元素。

全文小结

  本篇文章介绍了LinkedHashSet的原理和实现。LinkedHashSet继承了HashSet的高效性和LinkedHashMap的有序性,可以在保证元素唯一性的同时,还保留了元素的插入顺序。底层使用LinkedHashMap实现,使用双向链表维护了元素的插入顺序。在应用场景上,LinkedHashSet常用于缓存、配置文件和去重等场景。其优点在于高效的元素查找、插入和删除等操作,而缺点在于需要维护一个双向链表,可能会略微占用一些空间。在使用上,LinkedHashSet提供了一系列方法,如add、remove、contains等,同时还具备Iterator迭代器用于遍历集合中的元素。最后,我们也给出了一个使用LinkedHashSet的简单测试用例。

总结

  本文介绍了LinkedHashSet的概念、原理和实现。LinkedHashSet是Java中的一个集合类,它继承了HashSet的高效性和LinkedHashMap的有序性,同时支持高效的元素查找、插入和删除等操作。在应用场景上,LinkedHashSet常用于缓存、配置文件和去重等场景。其优点在于保证元素唯一性的同时,还保留了元素的插入顺序,同时具备高效的操作。类方法包括add、remove、contains等,同时还具备Iterator迭代器用于遍历集合中的元素。对于想要深入理解Java集合框架的同学,学习LinkedHashSet是必不可少的一部分。

  ...
  好啦,这期的内容就基本接近尾声啦,若你想学习更多,可以参考这篇专栏总结《「滚雪球学Java」教程导航帖》,本专栏致力打造最硬核 Java 零基础系列学习内容,🚀打造全网精品硬核专栏,带你直线超车;欢迎大家订阅持续学习。

附录源码

  如上涉及所有源码均已上传同步在「Gitee」,提供给同学们一对一参考学习,辅助你更迅速的掌握。

☀️建议/推荐你


  无论你是计算机专业的学生,还是对编程有兴趣的小伙伴,都建议直接毫无顾忌的学习此专栏「滚雪球学Java」,bug菌郑重承诺,凡是学习此专栏的同学,均能获取到所需的知识和技能,全网最快速入门Java编程,就像滚雪球一样,越滚越大,指数级提升。

  最后,如果这篇文章对你有所帮助,帮忙给作者来个一键三连,关注、点赞、收藏,您的支持就是我坚持写作最大的动力。


目录
相关文章
|
3天前
|
人工智能 自然语言处理 Java
FastExcel:开源的 JAVA 解析 Excel 工具,集成 AI 通过自然语言处理 Excel 文件,完全兼容 EasyExcel
FastExcel 是一款基于 Java 的高性能 Excel 处理工具,专注于优化大规模数据处理,提供简洁易用的 API 和流式操作能力,支持从 EasyExcel 无缝迁移。
46 9
FastExcel:开源的 JAVA 解析 Excel 工具,集成 AI 通过自然语言处理 Excel 文件,完全兼容 EasyExcel
|
10天前
|
存储 缓存 Java
Java 并发编程——volatile 关键字解析
本文介绍了Java线程中的`volatile`关键字及其与`synchronized`锁的区别。`volatile`保证了变量的可见性和一定的有序性,但不能保证原子性。它通过内存屏障实现,避免指令重排序,确保线程间数据一致。相比`synchronized`,`volatile`性能更优,适用于简单状态标记和某些特定场景,如单例模式中的双重检查锁定。文中还解释了Java内存模型的基本概念,包括主内存、工作内存及并发编程中的原子性、可见性和有序性。
Java 并发编程——volatile 关键字解析
|
8天前
|
Java 数据库连接 Spring
反射-----浅解析(Java)
在java中,我们可以通过反射机制,知道任何一个类的成员变量(成员属性)和成员方法,也可以堆任何一个对象,调用这个对象的任何属性和方法,更进一步我们还可以修改部分信息和。
|
11天前
|
监控 Java API
探索Java NIO:究竟在哪些领域能大显身手?揭秘原理、应用场景与官方示例代码
Java NIO(New IO)自Java SE 1.4引入,提供比传统IO更高效、灵活的操作,支持非阻塞IO和选择器特性,适用于高并发、高吞吐量场景。NIO的核心概念包括通道(Channel)、缓冲区(Buffer)和选择器(Selector),能实现多路复用和异步操作。其应用场景涵盖网络通信、文件操作、进程间通信及数据库操作等。NIO的优势在于提高并发性和性能,简化编程;但学习成本较高,且与传统IO存在不兼容性。尽管如此,NIO在构建高性能框架如Netty、Mina和Jetty中仍广泛应用。
26 3
|
11天前
|
安全 算法 Java
Java CAS原理和应用场景大揭秘:你掌握了吗?
CAS(Compare and Swap)是一种乐观锁机制,通过硬件指令实现原子操作,确保多线程环境下对共享变量的安全访问。它避免了传统互斥锁的性能开销和线程阻塞问题。CAS操作包含三个步骤:获取期望值、比较当前值与期望值是否相等、若相等则更新为新值。CAS广泛应用于高并发场景,如数据库事务、分布式锁、无锁数据结构等,但需注意ABA问题。Java中常用`java.util.concurrent.atomic`包下的类支持CAS操作。
43 2
|
1月前
|
存储 算法 Java
Java内存管理深度解析####
本文深入探讨了Java虚拟机(JVM)中的内存分配与垃圾回收机制,揭示了其高效管理内存的奥秘。文章首先概述了JVM内存模型,随后详细阐述了堆、栈、方法区等关键区域的作用及管理策略。在垃圾回收部分,重点介绍了标记-清除、复制算法、标记-整理等多种回收算法的工作原理及其适用场景,并通过实际案例分析了不同GC策略对应用性能的影响。对于开发者而言,理解这些原理有助于编写出更加高效、稳定的Java应用程序。 ####
|
2月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
87 2
|
3月前
|
缓存 Java 程序员
Map - LinkedHashSet&Map源码解析
Map - LinkedHashSet&Map源码解析
87 0
|
10天前
|
存储 设计模式 算法
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。 行为型模式分为: • 模板方法模式 • 策略模式 • 命令模式 • 职责链模式 • 状态模式 • 观察者模式 • 中介者模式 • 迭代器模式 • 访问者模式 • 备忘录模式 • 解释器模式
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
|
10天前
|
设计模式 存储 安全
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
结构型模式描述如何将类或对象按某种布局组成更大的结构。它分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者釆用组合或聚合来组合对象。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象结构型模式比类结构型模式具有更大的灵活性。 结构型模式分为以下 7 种: • 代理模式 • 适配器模式 • 装饰者模式 • 桥接模式 • 外观模式 • 组合模式 • 享元模式
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析

推荐镜像

更多