Java中的集合(六)继承Collection的Set接口

一、Set接口的简介

Set接口和List接口都是继承自Collection接口,它与Collection接口中功能基本一致,并没有对Collection接口的扩展,但是它比Collection接口更严格。Java中的Set接口类似于数学直观上的集合。

Set接口的特性

1、Set接口元素无序。存储和读取元素都是无序的;

2、Set接口存储的元素不能重复。

二、Set接口的类图结构

通过上面的类图可用看出:Set接口下一共有三个实现类:HashSet、LinkedHashSet和TreeSet

三、Set接口的常用方法

四、对象相等性

引用到堆上同一个对象的两个引用是相等的。如果对两个引用调用hashCode方法,会得到相同的结果,如果对象所属的类没有覆盖Object的hashCode方法的话,hashCode会返回每个对象特有的序号(java是依据对象的内存地址计算出的此序号),所以两个不同的对象的hashCode值是不可能相等的。

如果想让两个对象的引用相等,必须重写Object对象的hashCode()和equals()方法,因为Object的hashCode()方法返回的是该对象的内存地址,所以重写hashCode()方法,保证引用的两个对象具有相同的hashCode,然后通过equals()方法判断两个对象,返回true。

五、HashSet

(一)、简介及继承结构

1、简介

HashSet实现了Set接口,底层的数据结构是哈希表(HashMap实例),HashSet的元素是根据哈希表的哈希值存取的,由于实现Set接口,所以HashSet存储的元素是无序,不可重复的。

3、继承结构

通过结构图可以看出,HashSet继承自AbstractSet,实现了Set, Cloneable(JDK1.8*), java.io.Serializable这些接口。

    • 继承AbstractSet,实现了Set接口:提供了相关添加,删除,修改和遍历等功能;
    • 实现Cloneable接口:覆盖函数clone(),可以被克隆;
    • 实现Serializable接口:支持序列化和反序列化,可以通过序列化传输数据。

3、特性

    • 底层数据结构是哈希表(HashMap实例);
    • 元素无序(根据哈希表的哈希值存取);
    • 元素不可重复(通过hashCode和equals方法);
    • 线程不安全(非同步);
    • 集合元素可以是null,且只有一个null。

(二)、HashSet的构造方法

(三)、HashSet如何保证唯一性

HashSet通过元素的hashCode()和equals()方法判断元素是否重复,从而保证元素的唯一性。

当HashSet调用add(E e)方法时,元素e调用hashCode()方法获取哈希值,根据此哈希值判断集合中是否存在相同的哈希值,

1、调用hashCode()方法,哈希值不同的情况

  如果哈希值不同,则直接添加。

2、调用hashCode()方法,哈希值相同的情况

如果哈希值相同同,则取出集合中与此哈希值相同的对象,遍历这些对象时通过元素e调用equals()方法判断是否相同,不同则添加。

因此,当添加的对象时自定义类时,必须重写hashCode()和equals()方法,来确保对象具有相同哈希值。

六、LinkedHashSet

(一)、简介及继承结构

1、简介

LinkedHashSet直接继承自HashSet,底层数据结构由哈希表和链表组成。哈希表保证元素的唯一性,链表保证元素有序(存储和取出顺序是一致的)。

3、继承结构

LinkedHashSet继承自HashSet,所以继承结构和HashSet类似,可以参考HashSet,这里不再赘述。

3、特性

    • 底层数据结构有哈希表和链表组成(哈希表保证元素的唯一性,链表保证元素有序(存储和取出顺序是一致的))。
    • 元素有序(基于链表);
    • 元素不可重复(基于哈希表);
    • 线程不安全(非同步);
    • 允许有null值。

(二),LinkedHashSet的构造方法

(三)、LinkedHashSet的排序

LinkedHashSet使用LinkedHashMap对象存储元素,添加到LinkedHashSet的元素实际上当做LinkedHashMap的键保存起来。

LinkedHashMap的每一个键值对都是通过内部的静态类Entry<K, V>实例化的。这个 Entry<K, V>类继承了HashMap.Entry类。

这个静态类增加了两个成员变量,before和after来维护LinkedHasMap元素的插入顺序。这两个成员变量分别指向前一个和后一个元素,这让LinkedHashMap也有类似双向链表的表现。

通过上图LinkedHashMap的部分源码可以看出,LinkedHashMap定义了内部类Entry的两个变量before,after,维护了插入元素的顺序。定义了两个成员变量head,tail保存头节点和尾节点。

(四)、LinkedHashSet如何保证唯一性

LinkedHashSet通过LinkedHashMap存储对象,通过hashCode()和equals()方法保证元素的唯一性,过程与HashSet类似,可参考HashSet添加元素的过程,这里不再赘述。

(五)、LinkedHashSet与HashSet的区别

1、元素顺序。HashSet元素无序,LinkedHashSet元素有序;

2、数据结构。HashSet底层数据结构是哈希表(HashMap实例),LinkedHashSet底层数据结构是哈希表和链表;

3、性能。迭代遍历时,HashSet比不上LinkedHashSet,插入元素时,HashSet比LinkedHashSet强。

七、TreeSet

(一)、简介及继承结构

1、简介

TreeSet继承自Set接口,底层数据结构是二叉树,并且是红黑树,底层依赖于TreeMap,元素是有序,不可重复的。

3、继承结构

TreeSet 是继承AbstractSet,实现了Set接口,特性和HashSet结构类似。

TreeSet 实现了NavigableSet接口,意味着它支持一系列的导航方法。比如查找与指定目标最匹配项。

3、特性

    • 元素有序;
    • 元素不可重复;
    • 线程不安全的;
    • 允许有null值;
    • TreeSet不支持快速随机遍历,只能通过迭代器进行遍历。

(二)、TreeSet的构造方法

(三)、TreeSet排序

TreeSet是基于TreeMap实现的,底层数据结构是红黑树,支持两种排序方式:自然排序和定制排序。

Java中提供了Comparable和Comparator接口都是为了对类进行比较可以把Comparable理解为内部比较器,而Comparator是外部比较器,具体概念在Java中的集合(三)继承Collection的Queue接口 已经说明,感兴趣的可以点击查看。

1、自然排序

TreeSet会调用实现了Comparable接口的集合元素的compareTo(Objec o)方法来比较元素之间的大小关系,然后将集合元素按升序排列,这就是自然排序。

    • 返回正数:往二叉树的右边添加
    • 返回负数:往二叉树的左边添加
    • 返回 0 : 说明重复,不添加

2、定制排序

定制排序,集合元素必须要实现Comparator接口的int compare(T o1,T o2)方法,定制特有的排序方式。

(四)、TreeSet如何保证唯一性

TreeSet是Set的实现类,元素不可重复的原理和HashSet类似,具体可参考HashSet。

(五)、TreeSet与HashSet的区别

1、底层数据结构。HashSet是基于哈希表的,TreeSet是基于二叉树(红黑树的);

2、元素排序。HashSet元素是无序的,TreeSet元素是有序的;

(六)、TreeSet的数据结构:红黑树

1、红黑树的简介

红黑树是特殊的二叉查找树,意味着它满足二叉查找树的特征:任意一个节点所包含的键值,大于等于左孩子的键值,小于等于右孩子的键值。

除了具备该特性之外,红黑树还包括许多额外的信息。

红黑树的每个节点上都有存储位表示节点的颜色,颜色是红(Red)或黑(Black)。

2、特性

    • (1)每个节点或者是黑色,或者是红色。
    • (2)根节点是黑色。
    • (3)每个叶子节点是黑色。 (注意:这里叶子节点,是指为空的叶子节点!)
    • (4)如果一个节点是红色的,则它的子节点必须是黑色的。
    • (5)从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。

关于它的特性,需要注意的是:
第一,特性(3)中的叶子节点,是只为空(NIL或null)的节点。
第二,特性(5),确保没有一条路径会比其他路径长出俩倍。因而,红黑树是相对是接近平衡的二叉树。

3、树的旋转

红黑树的基本操作是添加删除旋转。在对红黑树进行添加或删除后,会用到旋转方法。为什么呢?道理很简单,添加或删除红黑树中的节点之后,红黑树就发生了变化,可能不满足红黑树的5条性质,也就不再是一颗红黑树了,而是一颗普通的树。而通过旋转,可以使这颗树重新成为红黑树。简单点说,旋转的目的是让树保持红黑树的特性。
旋转包括两种:左旋 和 右旋。下面分别对红黑树的基本操作进行介绍。

(1)、左旋

将子根节点变成子根节点的左节点

(2)、右旋

将子根节点变成子根节点的右节点

如下图解

4、添加操作

将一个节点插入到红黑树中,需要执行哪些步骤呢?首先,将红黑树当作一颗二叉查找树,将节点插入;然后,将节点着色为红色;最后,通过"旋转和重新着色"等一系列操作来修正该树,使之重新成为一颗红黑树。详细描述如下:

①: 将红黑树当作一颗二叉查找树,将节点插入。
       红黑树本身就是一颗二叉查找树,将节点插入后,该树仍然是一颗二叉查找树。也就意味着,树的键值仍然是有序的。此外,无论是左旋还是右旋,若旋转之前这棵树是二叉查找树,旋转之后它一定还是二叉查找树。这也就意味着,任何的旋转和重新着色操作,都不会改变它仍然是一颗二叉查找树的事实。
那接下来,通过以下三步骤,使这颗树重新成为红黑树!

②:将插入的节点着色为"红色"。
       为什么着色成红色,而不是黑色呢?为什么呢?在回答之前,我们需要重新温习一下红黑树的特性:
(1) 每个节点或者是黑色,或者是红色。
(2) 根节点是黑色。
(3) 每个叶子节点是黑色。 [注意:这里叶子节点,是指为空的叶子节点!]
(4) 如果一个节点是红色的,则它的子节点必须是黑色的。
(5) 从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。
      将插入的节点着色为红色,不会违背"特性(5)"!少违背一条特性,就意味着我们需要处理的情况越少。接下来,就要努力的让这棵树满足其它性质即可;满足了的话,它就又是一颗红黑树了。

③: 通过一系列的旋转或着色等操作,使之重新成为一颗红黑树。
       第二步中,将插入节点着色为"红色"之后,不会违背"特性(5)"。那它到底会违背哪些特性呢?
       对于"特性(1)",显然不会违背了。因为我们已经将它涂成红色了。
       对于"特性(2)",显然也不会违背。在第一步中,我们是将红黑树当作二叉查找树,然后执行的插入操作。而根据二叉查找数的特点,插入操作不会改变根节点。所以,根节点仍然是黑色。
       对于"特性(3)",显然不会违背了。这里的叶子节点是指的空叶子节点,插入非空节点并不会对它们造成影响。
       对于"特性(4)",是有可能违背的!
       那接下来,想办法使之"满足特性(4)",就可以将树重新构造成红黑树了。

5、删除操作

将红黑树内的某一个节点删除。需要执行的操作依次是:首先,将红黑树当作一颗二叉查找树,将该节点从二叉查找树中删除;然后,通过"旋转和重新着色"等一系列来修正该树,使之重新成为一棵红黑树。详细描述如下:

第一步:将红黑树当作一颗二叉查找树,将节点删除。
       这和"删除常规二叉查找树中删除节点的方法是一样的"。分3种情况:
① 被删除节点没有儿子,即为叶节点。那么,直接将该节点删除就OK了。
② 被删除节点只有一个儿子。那么,直接删除该节点,并用该节点的唯一子节点顶替它的位置。
③ 被删除节点有两个儿子。那么,先找出它的后继节点;然后把“它的后继节点的内容”复制给“该节点的内容”;之后,删除“它的后继节点”。在这里,后继节点相当于替身,在将后继节点的内容复制给"被删除节点"之后,再将后继节点删除。这样就巧妙的将问题转换为"删除后继节点"的情况了,下面就考虑后继节点。 在"被删除节点"有两个非空子节点的情况下,它的后继节点不可能是双子非空。既然"的后继节点"不可能双子都非空,就意味着"该节点的后继节点"要么没有儿子,要么只有一个儿子。若没有儿子,则按"情况① "进行处理;若只有一个儿子,则按"情况② "进行处理。

第二步:通过"旋转和重新着色"等一系列来修正该树,使之重新成为一棵红黑树。
        因为"第一步"中删除节点之后,可能会违背红黑树的特性。所以需要通过"旋转和重新着色"来修正该树,使之重新成为一棵红黑树。

红黑树是一种数据结构,本小节只是说明了一些概念,具体使用还需要深入了解。

八、HashSet、LinkedHashSet和TreeSet的异同点

(一)、不同点

1、HashSet存储元素是无序的,LinkedHashSet和TreeSet存储元素是有序的;

2、HashSet是基于哈希表,LinkedHashSet是基于哈希表和链表,TreeSet是基于红黑树;

3、LinkedHashSet是基于链表的排序(存取顺序一致),TreeSet可以使用自然排序,也可定制排序。

(二)、相同点

1、存储的元素是不可重复的;

2、线程不安全的;

3、遍历方式推荐使用迭代器遍历。

Java中的集合(六)继承Collection的Set接口的更多相关文章

  1. java中的集合:继承关系和简介

    1.继承关系图 2.Collection接口 Collection是最基本的集合接口,一个Collection代表一组Object,即Collection的元素(Elements).一些Collect ...

  2. Java面试题:Java中的集合及其继承关系

    关于集合的体系是每个人都应该烂熟于心的,尤其是对我们经常使用的List,Map的原理更该如此.这里我们看这张图即可: 1.List.Set.Map是否继承自Collection接口? List.Set ...

  3. Java学习--java中的集合框架、Collection接口、list接口

    与数组相比:1.数组的长度固定,而集合的长度可变2.数组只能通过下表访问元素,类型固定,而有的集合可以通过任意类型查找所映射的具体对象 java集合框架:collection(list序列,queue ...

  4. 分享知识-快乐自己:Java中各种集合特点

    Java中各种集合特点: Collection[单列集合]: List(有序,可重复): ArrayList: 底层数据结构是数组,查询快,增删慢.线程不安全,效率高. Vector: 底层数据结构是 ...

  5. JAVA笔记整理(九),JAVA中的集合

    在工作中,我们经常需要将多个对象集中存放,可以使用数组,但是数组的长度一旦固定之后是不可变的,为了保存数量确定的数据,我们可以使用JAVA中的集合. 在我看来,JAVA中的集合可以看作是一个特殊的数据 ...

  6. Java中的集合(五)继承Collection的List接口

    Java中的集合(五)继承Collection的List接口 一.List接口简介 List是有序的Collection的,此接口能够精确的控制每个元素插入的位置.用户能够根据索引(元素在List接口 ...

  7. Java中的集合(三)继承Collection的Queue接口

    Java中的集合(三)继承Collection的Queue接口 一.Queue介绍 Queue接口继承自Collection接口,是Java中定义的一种队列数据结构,元素是有序的(按插入顺序排序),先 ...

  8. Java中的集合(二)单列集合顶层接口------Collection接口

    Java中的集合(二)单列集合顶层接口------Collection接口 Collection是一个高度封装的集合接口,继承自Iterable接口,它提供了所有集合要实现的默认方法.由于Iterab ...

  9. Java中的集合框架-Collection(二)

    上一篇<Java中的集合框架-Collection(一)>把Java集合框架中的Collection与List及其常用实现类的功能大致记录了一下,本篇接着记录Collection的另一个子 ...

随机推荐

  1. python——import日常学习记录

    import为导入包,有两种方法,一个是import,一个是from ** import  ** import后紧跟着的是个模块,一般是一个.py文件下的类名: from *** import *** ...

  2. 软件——IDEA 超实用使用技巧分享

    前言 工欲善其事 ​必先利其器 最近受部门的邀请,给入职新人统一培训IDEA,发现有很多新人虽然日常开发使用的是IDEA,但是还是很多好用的技巧没有用到,只是用到一些基本的功能,蛮浪费IDEA这个优秀 ...

  3. GoF23:设计模式概述

    目录 学习设计模式的意义 GoF23 创建型模式(5种) 结构型模式(7种) 行为型模式(11种) OOP七大原则 开闭原则(总的纲领) 里氏替换原则 依赖倒置原则 单一职责原则 接口隔离原则 迪米特 ...

  4. 【Spark】如何用Spark查询IP地址?

    文章目录 需求 思路 ip地址转换为Long类型的两种方法 ip地址转换数字地址的原理 第一种方法 第二种方法 步骤 一.在mysql创建数据库表 二.开发代码 需求 日常生活中,当我们打开地图时,会 ...

  5. STM32 基于 CubeMX配置GPIO点亮LED灯(超级详细+图文并茂)

    我是一个只会点灯的菜鸟: 相关文章 [STM32系列汇总]小白博主的STM32实战快速进阶之路(持续更新) 文章目录 相关文章 1 前言 2 理论分析 2.1 LED 原理 2.2 板载资料 2.3 ...

  6. 【hdu1024】简单dp

    http://acm.hdu.edu.cn/showproblem.php?pid=1024 最大m字段和,题目就不多说了,经典dp 这题坑爹...首先不说明m的范围(n<=1000000),还 ...

  7. Asp.Net Core 3.1学习-读取、监听json配置文件(7)

    1.前言 文件配置提供程序默认的给我们提供了ini.json.Xml等.都是读取不同格式的文件.文件配置提供程序支持文件可寻.必选.文件变更的监视. 2.读取配置文件 主要运用的包:需要Ini.xml ...

  8. case when的使用-解决分表查数据给某一个字段

    一个表中存的是目前有效的菜单,另外一个表中存的是有效菜单的历史更改数据 需要查询历史数据的时候,带上访问的历史数据菜单名称 SELECT msg.msg_id, msg.from_user_name, ...

  9. SpringMVC 设置全局DateTime json返回格式

    对于部分返回DateTime的项目,只需要在指定属性上添加@JsonSerialize 使用自定义的json转换格式即可自定义返回DateTime格式 但是对于项目中返回有多个DateTime字段来说 ...

  10. C++语言字符串处理函数

    C++语言提供了比C语言更丰富的字符串处理功能.它可以在字符串上经行输入,输出,合并,修改,比较,转换,复制,搜索等操作.使用这些现成的功能可以大大减少我们的编程的负担. 输入和输出的字符串函数,如p ...