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

时间:2023-04-14 14:33:26

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

一、Set接口的简介

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

Set接口的特性

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

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

二、Set接口的类图结构

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

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

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

三、Set接口的常用方法

Java中的集合(六)继承Collection的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、继承结构

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

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

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

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

3、特性

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

(二)、HashSet的构造方法

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

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

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

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

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

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

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

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

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

六、LinkedHashSet

(一)、简介及继承结构

1、简介

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

3、继承结构

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

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

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

3、特性

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

(二),LinkedHashSet的构造方法

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

(三)、LinkedHashSet的排序

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

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

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

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

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

通过上图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、继承结构

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

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

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

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

3、特性

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

(二)、TreeSet的构造方法

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

(三)、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),确保没有一条路径会比其他路径长出俩倍。因而,红黑树是相对是接*衡的二叉树。

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

3、树的旋转

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

(1)、左旋

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

(2)、右旋

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

如下图解

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

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、遍历方式推荐使用迭代器遍历。