四大集合之Set

2024-09-01 09:44
文章标签 set 集合 四大

本文主要是介绍四大集合之Set,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一、Set基础知识

1. Set集合

1.1 HashSet

Set集合区别于其他三大集合的重要特性就是元素具有唯一性,南友们记不住这个特性的话,有个易记的方法。Set集合为什么要叫Set呢?因为Set集合的命名取自于我们小学数学里的集合论(Set Theory),数学集合一个很重要的概念就是每个元素的值都互不相同。

Set集合常见的有实例有:HashSet、LinkedHashSet、TreeSet,南哥先缕一缕HashSet。

// HashSet类源码
public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, java.io.Serializable {...}

HashSet底层实现其实是基于HashMap,HashMap的特点就是Key具有唯一性,这一点被HashSet利用了起来,每一个HashMap的Key对应的就是HashSet的元素值。来看看官方源码的解释。

此类实现Set接口,由哈希表(实际上是HashMap实例)支持。它不保证集合的迭代顺序;特别是,它不保证顺序随时间保持不变。此类允许null元素。

我们创建一个HashSet对象,实际上底层创建了一个HashMap对象。

    // HashSet构造方法源码public HashSet() {map = new HashMap<>();}

HashSet一共提供了以下常用方法,不得不说HahSet在业务开发中还是用的没那么多的,南哥在框架源码上看HashSet用的就比较多,比如由Java语言实现的zookeeper框架源码。

(1)添加元素

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

我们看上面add方法的源码,是不是调用了HashMap的put方法呢?而put方法添加的Key是HashSet的值,Val则是一个空的Object对象。PRESENT是这么定义的。

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

(2)判断元素是否存在

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

HashSet的contains方法同样是调用HashMap判断Key是否存在的方法:containsKey

(3)移除元素

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

1.2 LinkedHashSet

接着轮到LinkedHashSet,同为Set集合之一,它和上文的HashSet有什么区别?南哥卖个关子。

源码对LinkedHashSet的解释。

Hash table and linked list implementation of the Set interface, with predictable iteration order. This implementation differs from HashSet in that it maintains a doubly-linked list running through all of its entries. This linked list defines the iteration ordering, which is the order in which elements were inserted into the set (insertion-order).

源码的大概意思就是:Set接口的哈希表和链表实现,具有可预测的迭代顺序。此实现与HashSet的不同之处在于,它维护一个贯穿其所有条目的双向链表。此链表定义迭代顺序,即**元素插入集合的顺序 (**插入顺序)。

底层数据结构是一条双向链表,每个元素通过指针进行相连,也就有了按插入顺序排序的功能。

知道了LinkedHashSet的特性,看看他的构造方法。

    /*** 构造一个新的、空的链接哈希集,具有默认初始容量(16)和负载因子(0.75)。*/public LinkedHashSet() {super(16, .75f, true);}

这个super方法向上调用了底层C语言源码实现的LinedHashMap的构造方法。LinkedHashMap的特点就是元素的排序是根据插入的顺序进行排序,那LinkedHashSet也就继承了这个特性。

    // C语言源码HashSet(int initialCapacity, float loadFactor, boolean dummy) {map = new LinkedHashMap<>(initialCapacity, loadFactor);}

LinkedHashSet的常见方法和HashSet一样,同样是add()、contains()、remove(),这里我写个简单的Demo。

    public static void main(String[] args) throws IOException {LinkedHashSet<Integer> set = new LinkedHashSet<>();set.add(1);System.out.println(set.contains(1));set.remove(1);System.out.println(set.contains(1));}
# 运行结果
true
false

1.3 TreeSet

轮到你了,TreeSet。我们南友们很好奇为什么他叫TreeSet?

因为他是基于TreeMap实现的。。。

但根本原因不是,TreeMap的底层是通过红-黑树数据结构来实现自然排序,那TreeSet也就继承了这个特性。

官方源码对TreeSet的解释:

基于TreeMap的NavigableSet实现。元素使用其自然顺序进行排序,或者根据使用的构造函数,使用创建集合时提供的Comparator进行排序。

源码解释告诉我们,TreeSet和HashSet、LinkedHashSet不同的特性在于,元素既不像HashSet一样无序,也不是像LinkedHashSet一样是以插入顺序来排序,它是根据元素的自然顺序来进行排序。

b、c、a这三个元素插入到TreeSet中,自然顺序就和字母表顺序一样是:a、b、c

    public static void main(String[] args) throws IOException {TreeSet<String> treeSet = new TreeSet<>();treeSet.add("b");treeSet.add("c");treeSet.add("a");System.out.println(treeSet);}
# 运行结果
[a, b, c]

TreeSet除了拥有以下的add()、contains()、remove()方法。

    // 如果指定元素尚不存在,则将其添加到此集合中。public boolean add(E e) {return m.put(e, PRESENT)==null;}// 如果此集合包含指定元素,则返回true public boolean contains(Object o) {return m.containsKey(o);}// 如果存在指定元素,则从此集合中移除该元素。public boolean remove(Object o) {return m.remove(o)==PRESENT;}

值得提出来的是,TreeSet还拥有first()、last(),可以方便我们提取出第一个、最后一个元素。

    // 返回集合中的第一个元素。public E first() {return m.firstKey();}// 返回集合中的最后一个元素。public E last() {return m.lastKey();}

1.4 TreeSet自定义排序

TreeSet的自定义排序我们要利用Comparator接口,通过向TreeSet传入自定义排序规则的Comparator来实现。

官方源码是这么解释的,南友们看一看。

    // 构造一个新的空树集,根据指定的比较器进行排序。// 插入到集合中的所有元素都必须能够通过指定的比较器相互比较: comparator. compare(e1, e2)不得对集合中的任何元素e1和e2抛出ClassCastException 。// 如果用户尝试向集合中添加违反此约束的元素,则add调用将抛出ClassCastException public TreeSet(Comparator<? super E> comparator) {this(new TreeMap<>(comparator));}

传入Comparator接口时,我们还需要定义compare方法的游戏规则:如果compare方法比较两个元素的大小,返回正整数代表第一个元素 > 第二个元素、返回负整数代表第一个元素 < 第二个元素、返回0代表第一个元素 = 第二个元素。

下面我写了一个Demo,Comparator接口的规则是这样:人的岁数越小,那么他排名越靠前。

public class JavaProGuideTest {public static void main(String[] args) {TreeSet set = new TreeSet(new Comparator() {public int compare(Object o1, Object o2) {Person p1 = (Person)o1;Person p2 = (Person)o2;return (p1.age > p2.age) ? 1 : (p1.age < p2.age) ? -1 : 0;}});set.add(new Person(5));set.add(new Person(3));set.add(new Person(6));System.out.println(set);}@Data@AllArgsConstructorprivate static class Person {int age;}
}
# 执行结果
[JavaProGuideTest.Person(age=3), JavaProGuideTest.Person(age=5), JavaProGuideTest.Person(age=6)]

二、Set常见面试题

1. 你能解释一下 Set 和 List 的区别吗?特别是它们在使用场景上的差异?

回答: SetList 是 Java 集合框架中的两个核心接口,它们的主要区别在于是否允许重复元素。List 允许存储重复的元素,并且元素是有序的,可以通过索引访问。Set 则不允许存储重复元素,元素的顺序不固定(具体取决于实现类)。在实际应用中,当你需要保证集合中的元素唯一时,使用 Set,例如处理用户权限、存储唯一标识符等场景;而 List 适用于需要频繁访问元素或保持插入顺序的场景,如订单列表、待办事项等。

2. 你如何选择使用 HashSet、LinkedHashSet 和 TreeSet?分别在什么情况下使用它们?

回答: HashSet 是最常用的 Set 实现,适用于对顺序没有要求且需要高效查询的场景。LinkedHashSet 保留了插入顺序,适用于需要保留元素插入顺序且对性能要求不太高的场景。TreeSet 则保证元素的排序顺序,适用于需要对集合中的元素进行排序的场景,如存储按字母顺序排列的单词、按照优先级排序的任务等。

3. 假如我们有一个包含大量元素的 List,现在需要去重并保持顺序,你会选择什么集合实现?为什么?

回答: 我会选择 LinkedHashSet。因为 LinkedHashSet 不仅能去重,还能保持元素的插入顺序。通过将 List 转换为 LinkedHashSet,可以轻松地去除重复元素,同时保留原始顺序。

4. 在 HashSet 中插入一个元素时,底层具体做了哪些操作?

回答: 当在 HashSet 中插入一个元素时,底层首先通过 hashCode() 方法计算该元素的哈希值,然后将这个哈希值用于定位元素在哈希表中的存储位置。如果该位置已经有元素,则会调用 equals() 方法来比较新元素和现有元素是否相等。如果相等,则不插入新元素;如果不相等,则使用链表或红黑树结构解决哈希冲突,最后将新元素插入到适当位置。

5. 能详细解释一下 TreeSet 是如何进行排序的吗?如果我们有自定义对象呢?

回答: TreeSet 是基于红黑树实现的,默认情况下使用元素的自然顺序(通过 Comparable 接口的 compareTo() 方法)进行排序。如果是自定义对象,可以实现 Comparable 接口并重写 compareTo() 方法,或者在创建 TreeSet 时传入一个 Comparator 来定义排序逻辑。例如,假设我们有一个 Person 类,按照年龄排序,可以在 compareTo() 方法中定义比较逻辑,或者通过 Comparator 传入 TreeSet

6. 在实际项目中,是否遇到过 HashSet 无法去重的情况?是什么原因造成的?

回答: 是的,可能会遇到这种情况。这通常是由于自定义对象没有正确实现 equals()hashCode() 方法造成的。HashSet 依赖这两个方法来判断元素的唯一性,如果它们没有正确实现,即使两个对象内容相同,HashSet 也会认为它们是不同的元素,导致无法去重。解决方法是确保 equals()hashCode() 方法按照相同的逻辑比较对象的所有关键属性。

7. 你如何在 Set 中存储自定义对象,同时确保没有重复对象?

回答: 要在 Set 中存储自定义对象并确保没有重复对象,必须重写对象类的 equals()hashCode() 方法。equals() 方法用于判断两个对象是否相等,而 hashCode() 方法则用于计算对象的哈希值。在重写这些方法时,通常需要确保 equals() 方法判断的关键字段都参与 hashCode() 的计算,以确保哈希值和相等性逻辑一致。

8. 什么情况下你会选择使用 EnumSet?它和普通的 Set 有什么不同?

回答: EnumSet 是一个专门用于存储枚举类型的高效 Set 实现。它的实现基于位向量,因而非常高效,特别是当你需要存储一组枚举常量时。相比普通的 SetEnumSet 占用更少的内存,性能也更好。例如,在权限管理系统中,如果有一组固定的权限(枚举),可以使用 EnumSet 来高效管理这些权限。

9. 为什么 TreeSet 不允许插入 null 元素?

回答: TreeSet 基于红黑树实现,它需要对插入的元素进行排序,而 null 元素无法进行比较(因为 compareTo()Comparator 都无法处理 null),因此在插入 null 时会抛出 NullPointerException。这与 HashSet 可以插入 null 元素形成了对比,因为 HashSet 不需要对元素进行排序。

10. 假如我们需要创建一个线程安全的 Set,你会怎么做?

回答: 有几种方法可以创建线程安全的 Set。一种方法是使用 Collections.synchronizedSet(new HashSet<>()),这会返回一个同步的 Set,所有对它的访问都会自动加锁。另一种更现代的方法是使用 ConcurrentHashMap.newKeySet(),它基于 ConcurrentHashMap 实现,允许并发访问,并且性能更好。

11. HashSet 的默认加载因子是 0.75,你能解释一下这个值的含义吗?它对性能有什么影响?

回答: 加载因子表示 HashSet 在需要扩容前能填满的比例。默认值 0.75 表示当 HashSet 中的元素数量达到容量的 75% 时,它会自动扩容以保持性能。较高的加载因子可以减少内存的使用,但会增加冲突率,从而降低查找和插入的性能。较低的加载因子会增加内存使用,但减少冲突率,提升性能。一般情况下,0.75 是在性能和内存使用之间的一个折中选择。

12. 你能描述一下 Set 和 Map 之间的关系吗?为什么我们说 HashSet 实际上是基于 HashMap 实现的?

回答Set 和 Map 是 Java 集合框架的两种不同接口。Set 用于存储不重复的元素,而 Map 用于存储键值对。HashSet 是基于 HashMap 实现的,实际上,HashSet 通过 HashMap 来存储元素,每个元素作为 HashMap 的键(key),而值(value)则是一个固定的常量对象。因此,HashSet 在内部通过 HashMap 的 put() 方法来确保元素唯一。

13. 在并发环境中,使用 ConcurrentSkipListSet 有什么优势?

回答ConcurrentSkipListSet 是一个线程安全的 Set 实现,它基于跳表数据结构。这种结构不仅支持并发访问,而且在执行插入、删除和查找操作时具有较高的性能。与传统的锁机制不同,跳表允许多个线程并发地操作不同的部分,从而减少了锁争用,提升了并发性能。ConcurrentSkipListSet 特别适用于需要自然排序且需要支持高并发的场景。

14. 你是否遇到过 HashSet 中的元素顺序不一致的情况?为什么会这样?

回答: 是的,HashSet 的元素顺序是不固定的。这是因为 HashSet 的底层实现是基于哈希表的,元素的位置取决于其哈希值。而哈希值的分布可能随着元素的增加、删除或哈希表的扩容而发生变化,从而导致元素顺序的变化。因此,HashSet 不适合用在需要保持元素顺序的场景中,如果需要顺序,可以使用 LinkedHashSet

15. 如何在一个 Set 中查找最大或最小元素?

回答: 如果使用的是 TreeSet,可以直接使用 first() 和 last() 方法来获取最小和最大元素,因为 TreeSet 是有序的。对于无序的 Set(如 HashSet 或 LinkedHashSet),你需要手动遍历集合并使用比较器来找到最大或最小元素

16. 如何判断两个 Set 是否相等?

回答: 两个 Set 被认为是相等的,当且仅当它们包含的元素相同,且元素的数量也相同。可以使用 Set 的 equals() 方法来判断两个 Set 是否相等。这个方法会检查两个集合是否具有相同的元素(无论顺序),如果是,则返回 true。

17. 你能解释一下 WeakHashSet 是什么吗?它在什么场景下使用?

回答: 实际上,Java 标准库中并没有 WeakHashSet,但可以通过 Collections.newSetFromMap(new WeakHashMap<>()) 来创建一个 WeakHashSet。这种 Set 使用 WeakHashMap 来存储元素的引用,当元素不再被其他强引用所引用时,GC 会自动回收这些元素。这种 Set 适用于缓存等场景,可以防止内存泄漏

18. 如果需要对一个非常大的 Set 进行操作,比如合并、交集或差集,如何处理会更高效?

回答: 对于非常大的 Set,可以使用并行流(parallelStream())来提高操作效率。并行流可以利用多核处理器同时处理多个元素,从而加快合并、交集和差集的计算。另外,如果这些操作频繁且数据量非常大,使用高效的并发集合如 ConcurrentSkipListSet 也可以帮助提升性能

19. 在 Java 8 中,如何使用流(Stream)操作 Set?

回答: 在 Java 8 中,可以使用 Set.stream() 方法将 Set 转换为流,然后使用流的各种操作如 filtermapcollect 等进行操作。例如,如果你想对 Set 中的每个元素进行处理并收集结果,可以这样做: java Set<String> set = new HashSet<>(Arrays.asList("a", "b", "c")); Set<String>result=set.stream().map(String::toUpperCase).collect(Collectors.toSet()); 这样你就可以很方便地使用流来操作 Set

20. 在面试中经常问到如何优化 Set 操作,你会给出哪些建议?

回答: 优化 Set 操作的建议包括: - 根据需求选择合适的 Set 实现,如 HashSetLinkedHashSetTreeSet 等。 - 合理设置 HashSet 的初始容量和加载因子,以减少扩容次数。 - 对于频繁的集合操作如并集、交集等,可以使用并行流来提升性能。 - 使用 EnumSet 来处理枚举类型的集合,能大幅度减少内存消耗和提高性能。 - 在多线程环境下使用线程安全的集合如 ConcurrentSkipListSet 或通过 Collections.synchronizedSet() 来保证安全性。

让我们一起学习,一起进步!期待在评论区与你们见面。

祝学习愉快!

这篇关于四大集合之Set的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1126585

相关文章

基于Redis有序集合实现滑动窗口限流的步骤

《基于Redis有序集合实现滑动窗口限流的步骤》滑动窗口算法是一种基于时间窗口的限流算法,通过动态地滑动窗口,可以动态调整限流的速率,Redis有序集合可以用来实现滑动窗口限流,本文介绍基于Redis... 滑动窗口算法是一种基于时间窗口的限流算法,它将时间划分为若干个固定大小的窗口,每个窗口内记录了该时间

高效录音转文字:2024年四大工具精选!

在快节奏的工作生活中,能够快速将录音转换成文字是一项非常实用的能力。特别是在需要记录会议纪要、讲座内容或者是采访素材的时候,一款优秀的在线录音转文字工具能派上大用场。以下推荐几个好用的录音转文字工具! 365在线转文字 直达链接:https://www.pdf365.cn/ 365在线转文字是一款提供在线录音转文字服务的工具,它以其高效、便捷的特点受到用户的青睐。用户无需下载安装任何软件,只

uva 11178 计算集合模板题

题意: 求三角形行三个角三等分点射线交出的内三角形坐标。 代码: #include <iostream>#include <cstdio>#include <cstdlib>#include <algorithm>#include <cstring>#include <cmath>#include <stack>#include <vector>#include <

poj 3050 dfs + set的妙用

题意: 给一个5x5的矩阵,求由多少个由连续6个元素组成的不一样的字符的个数。 解析: dfs + set去重搞定。 代码: #include <iostream>#include <cstdio>#include <set>#include <cstdlib>#include <algorithm>#include <cstring>#include <cm

Collection List Set Map的区别和联系

Collection List Set Map的区别和联系 这些都代表了Java中的集合,这里主要从其元素是否有序,是否可重复来进行区别记忆,以便恰当地使用,当然还存在同步方面的差异,见上一篇相关文章。 有序否 允许元素重复否 Collection 否 是 List 是 是 Set AbstractSet 否

论文翻译:ICLR-2024 PROVING TEST SET CONTAMINATION IN BLACK BOX LANGUAGE MODELS

PROVING TEST SET CONTAMINATION IN BLACK BOX LANGUAGE MODELS https://openreview.net/forum?id=KS8mIvetg2 验证测试集污染在黑盒语言模型中 文章目录 验证测试集污染在黑盒语言模型中摘要1 引言 摘要 大型语言模型是在大量互联网数据上训练的,这引发了人们的担忧和猜测,即它们可能已

多路转接之select(fd_set介绍,参数详细介绍),实现非阻塞式网络通信

目录 多路转接之select 引入 介绍 fd_set 函数原型 nfds readfds / writefds / exceptfds readfds  总结  fd_set操作接口  timeout timevalue 结构体 传入值 返回值 代码 注意点 -- 调用函数 select的参数填充  获取新连接 注意点 -- 通信时的调用函数 添加新fd到

如何掌握面向对象编程的四大特性、Lambda 表达式及 I/O 流:全面指南

这里写目录标题 OOP语言的四大特性lambda输入/输出流(I/O流) OOP语言的四大特性 面向对象编程(OOP)是一种编程范式,它通过使用“对象”来组织代码。OOP 的四大特性是封装、继承、多态和抽象。这些特性帮助程序员更好地管理复杂的代码,使程序更易于理解和维护。 类-》实体的抽象类型 实体(属性,行为) -》 ADT(abstract data type) 属性-》成

Java基础回顾系列-第六天-Java集合

Java基础回顾系列-第六天-Java集合 集合概述数组的弊端集合框架的优点Java集合关系图集合框架体系图java.util.Collection接口 List集合java.util.List接口java.util.ArrayListjava.util.LinkedListjava.util.Vector Set集合java.util.Set接口java.util.HashSetjava

【408数据结构】散列 (哈希)知识点集合复习考点题目

苏泽  “弃工从研”的路上很孤独,于是我记下了些许笔记相伴,希望能够帮助到大家    知识点 1. 散列查找 散列查找是一种高效的查找方法,它通过散列函数将关键字映射到数组的一个位置,从而实现快速查找。这种方法的时间复杂度平均为(