Java单列集合——Set

文章详细介绍了Java中的Set集合,包括其无序、不重复、无索引的特点,主要实现类如HashSet、LinkedHashSet和TreeSet的特性。HashSet的底层基于哈希表,通过哈希值定位元素,保证不重复。LinkedHashSet通过双向链表保持插入顺序。TreeSet利用红黑树实现排序。此外,文章还讨论了各种场景下的集合选择。
摘要由CSDN通过智能技术生成

目录

1. Set集合的特点

2. Set集合的实现类

3. Set接口中的方法

4. Set的遍历方法

5. HashSet底层原理

6. 关于HashSet的几个问题

7. LinkedHashSet底层原理

8. TreeSet详解

9. 单列集合的使用场景


上帖我们讲述了Java中的其中一种单列集合List,这期我们来说说Java中的另一种单列集合Set。

1. Set集合的特点

(1)无序:存取顺序不一致;

(2)不重复:不允许有相同元素的出现,可以用来去重;

(3)无索引:没有关于索引的方法,所以List中关于索引的方法Set都不能用,也不能使用for循环遍历,不能通过索引存取元素。

2. Set集合的实现类

Set的实现类主要有以下三个:

(1)HashSet:无序,不重复,无索引;

(2)LinkedHashSet:因为加入了链表,有序,不重复,无索引;

(3)TreeSet:可排序,不重复,无索引。

3. Set接口中的方法

因为Set也是单列集合Collection中的一种,所以方法基本与Collection中的方法一致。这里还是简单罗列一下吧

 注意!!!

因为Set没有索引,这里边的删除方法只能根据对象删除,与List稍有区别。

4. Set的遍历方法

Set有三种遍历方式

(1)迭代器遍历;

(2)增强for遍历;

(3)Lambda表达式遍历。

因为在上期帖子List集合我们已经说过了怎样实现上述的三种遍历方法,所以这里不再做说明,有不了解的可以点下方链接去看看。

 Java单列集合——List_m0_70325779的博客-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/m0_70325779/article/details/131113180?spm=1001.2014.3001.5501

5. HashSet底层原理

(1)HashSet底层采用哈希表存储数据;

(2)哈希表是一种增删改查数据性能都很好的数据结构;

哈希表的组成在不同的JDK版本中也略显不同

JDK1.8之前:数组+链表

JDK1.8之后:数组+链表+红黑树

哈希表中,有一个哈希值,非常重要

如下图所示是一个哈希表,暂时所有元素都为空, 

以下性质需要掌握

(1)创建一个长度为16,默认加载因子为0.75的数组,数组名为table;

(2)当我们向哈希表中去添加元素时,它不是向数组那样从数组第一个位置挨个添加元素的,而是通过哈希值与数组的长度计算出它应该存放的位置;

(3)存放时,会判断当前位置是否为null,如果是则直接存入;

(4)如果位置不为null,表示有元素,则会调用equal()方法比较属性值;

(5)判断得出结果,如果一样则不存,不一样则存入数组形成链表;

这里是对上述的补充:

1. 这里JDK8以前,新元素存入时,会采用头插法,插在老元素的头部,老元素下移,形成链表;

2. 在JDK8以后,新元素存入时,采用尾插法,插在老元素的尾部,老元素位置不动,形成链表;

3. 这里我们再来说加载因子,当我们的数组中存入16 * 0.75 = 12 个元素的时候,那么此时数组就会扩容成原来的2倍;

4. 当链表的长度大于8且数组的长度大于64时,链表会转换成红黑树,从而提高查找效率。

关于什么是红黑树以及红黑树的性质,可以浏览我之前的文章 “数据结构——红黑树”,点击下方链接浏览。

(3条消息) 数据结构——红黑树_m0_70325779的博客-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/m0_70325779/article/details/131393598?spm=1001.2014.3001.55025. 如果集合中存贮的是自定义对象,必须要重写equals()和hashcode()方法,原因可以参照我之前的文章结合理解,链接如下:

(3条消息) 为什么重写equals方法之后必须要重写hashCode_m0_70325779的博客-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/m0_70325779/article/details/130809588?spm=1001.2014.3001.5501

6. 关于HashSet的几个问题

6.1 HashSet是怎样添加元素的呢?

答:  当想要存储一个元素时,先计算它的哈希值,再根据哈希值判断它应该存放的位置。

6.2 HashSet为什么存和取的顺序不一样?

答: 我们可以设想当我们存入了一个字符串 a = “abcdefg”,根据计算哈希值,它放在哈希表的最后,后来存入了一个字符串 b = “hijklmn” ,根据计算哈希值,它放在哈希表的最前面,当我们去取字符串a时,他是要从头遍历哈希表的,在第二次会取到字符串a,而存入时是率先存入的。这样看就很明显的说明了为什么存入的顺序和取出的顺序不一样。

6.3 HashSet为什么没有索引?

答: 因为哈希表它不像数组那样,里面还包含着链表以及红黑树,加入哈希表中第二个位置是一个链表,存储了三个元素,那么这三个元素的索引都是2吗?而且我们还没有考虑红黑树的情况,这样很明显是不可以的,所以在哈希表中就干脆取消了索引这个性质。

6.4 HashSet是利用什么机制保证数据的不可重复性的?

答: equals()方法和hashCode()方法,当想要存储一个元素时,先计算它的哈希值,再根据哈希值判断它应该存放的位置,有元素则进行equals()比较,如果是链表或红黑树,要对其中的每一个元素进行equals()比较,不同则插入。从而达到了元素的不可重复性。

7. LinkedHashSet底层原理

LinkedHashSet的特点:有序,不重复,无索引。

相比于HashSet,从无序变成了有序,这里的有序指的是存入和取出的顺序是有序的。

为什么呢?想必大家通过名字就能猜出来个大概了。LinkedHashSet的前缀Linked,说明在HashSet的基础上添加了一个链表,而且这个链表是双向链表。

这里我来距离简单说明是如何有序的,如下图,四个元素和一个哈希表

 添加第一个元素,找到自己所在的位置,因为为null,直接加入,如下

 添加第二个元素,找到自己的位置,直接加入,然后,通过双向链表的形式与第一次插入的元素形成关联,如下所示

 同理,插入第三个元素,找到自己的位置,再与第二个插入的元素形成链表指向,如下

插入第四个元素时,也是如此,如下所示

 插入完成后,当我们遍历LinkedHashSet时,它就不会像HashSet那样从第一个位置从头便利哈希表,而是那个元素最先插入,从哪个元素开始遍历,以此遍历直到得到自己想要的元素,从而达到了存取的有序性。

8. TreeSet详解

8.1 TreeSet的特点:不重复,无索引,可排序。

可排序:按照元素的默认规则,(从小到大)排列。

8.2 TreeSet集合的底层结构

TreeSet的底层是基于红黑树这种数据结构实现排序的,增删改查性能都很好。

8.3 TreeSet集合的默认排序规则

(1)对于数值类型:Integet,Double,默认按照从小到大的顺序排列;

(2)对于字符,字符串类型:按照字符在ASCII码表中的数字升序排序。这里字符的比较,是一位一位的比较,先比较第一位谁大谁小,小的排前面,第一位一样再比较第二位,谁小排前面,后面同理。

8.4 TreeSet的两种比较方式

8.4.1 方式一:默认排序/自然排序(javaBean类实现Comparable接口指定比较规则)

如下图,现要对student类进行排序,实现Comparable接口,

 

 然后我们就可以在Comparable方法中指定我们自己想要的比较规则,假设我这里只看年龄,修改后代码如下所示:

 这里有一点需要注意,TreeSet底层是红黑树,所以不需要强制重写HashCode()和equals()方法。这里需要了解红黑树的性质与插入规则,不懂的可以看我之前的文章,点击下方链接即可(3条消息) 数据结构——红黑树_m0_70325779的博客-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/m0_70325779/article/details/131393598?spm=1001.2014.3001.5501

上面我们定义的是按照年龄排序,方法返回值为int,这里分为三种情况

当int > 0(即返回值为正数时,该元素存到当前元素的右边);

当int < 0(即返回值为负数时,该元素存到当前元素的左边);

当int = 0 (表明当前元素已经存在,会舍弃不存)

这里要知道,当前节点首先指红黑树的根节点,然后从根一步步进行判断,找到自己应该插入的位置。

8.4.2 方式二:比较器排序(创建TreeSet的时候,传递比较器指定Comparator规则)

这两种比较器的使用原则:默认使用第一种比较器,如果第一种无法满足我们的需求,就会使用第二种。

来看线面两个练习题,我们就能更好地理解它了。

练习1:

 分析题目后很明显可以发现,原有的比较规则一已经无法达到我们想要的目的了,我们接着看,其实String类中已经帮我们重写定义好了排序规则,如下,

 String类实现了Comparable<>接口,

 上面就是我们的排序规则一

但我们现在要添加另一种排序规则,改Java的源码似乎不太合适吧。

这里我们可以采用TreeSet的另一个构造方法,它需要传一个比较器,但这个比较器Comparator是一个接口,所以我们要对它进行实现。实现后如下所示:

o1是要插入的元素,o2是红黑树中已经存在的元素;

 这里我们先把o1与o2的长度作比较,然后下面做判断,如果等于零,再调用String类中已经定义好的排序规则,如果不等于0,直接返回 i 值,插入到红黑树中。

练习2:

 这里分析得知,也是需要重新定义比较规则的,我们再Student类中实现Comparable<>接口,如下:

9. 单列集合的使用场景

(1)如果想要集合中的元素可重复

用ArrayList集合,基于数组的(也是实际开发中用的最多的)。

(2)如果想要集合中的元素可重复,而且增删操作远多于查询

用LinkedList集合,基于链表的。

(3)如果想对集合中的元素去重

用HashSet集合,基于哈希表的(实际开发中也用得最多)。

(4)如果想对集合中的元素去重,而且保证存取有序

用LinkedHashSet集合,基于哈希表和链表,但效率要比HashSet低。

(5)想要对集合中的元素进行排序

用TreeSet集合,基于红黑树。后续也可以用List集合实现排序。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值