|
1 | 1 | ---
|
2 |
| -title: 容器之 Set |
3 |
| -date: 2018/05/31 |
| 2 | +title: Java 容器之 Set |
| 3 | +date: 2018/06/28 |
4 | 4 | categories:
|
5 | 5 | - javase
|
6 | 6 | tags:
|
| 7 | +- java |
7 | 8 | - javase
|
8 |
| -- collection |
| 9 | +- container |
9 | 10 | ---
|
10 | 11 |
|
11 |
| -# 容器之 Set |
| 12 | +# Java 容器之 Set |
12 | 13 |
|
13 |
| -<!-- TOC --> |
| 14 | +<!-- TOC depthFrom:2 depthTo:2 --> |
14 | 15 |
|
15 |
| -- [HashSet](#hashset) |
16 |
| - - [HashSet 要点](#hashset-要点) |
17 |
| - - [HashSet 源码](#hashset-源码) |
18 |
| -- [TreeSet](#treeset) |
19 |
| - - [TreeSet 要点](#treeset-要点) |
20 |
| - - [TreeSet 源码](#treeset-源码) |
21 |
| -- [EnumSet](#enumset) |
22 |
| - - [EnumSet 要点](#enumset-要点) |
| 16 | +- [Set 架构](#set-架构) |
| 17 | +- [Set 接口](#set-接口) |
| 18 | +- [SortedSet 接口](#sortedset-接口) |
| 19 | +- [NavigableSet 接口](#navigableset-接口) |
| 20 | +- [AbstractSet 抽象类](#abstractset-抽象类) |
| 21 | +- [HashSet 类](#hashset-类) |
| 22 | +- [TreeSet 类](#treeset-类) |
| 23 | +- [LinkedHashSet 类](#linkedhashset-类) |
| 24 | +- [EnumSet 类](#enumset-类) |
| 25 | +- [资料](#资料) |
23 | 26 |
|
24 | 27 | <!-- /TOC -->
|
25 | 28 |
|
| 29 | +## Set 架构 |
26 | 30 |
|
27 |
| -Set 集合与 Collection 集合基本相同,没有提供任何额外的方法。 |
| 31 | +<div align="center"> |
| 32 | +<img src="https://raw.githubusercontent.com/dunwu/javase-notes/master/images/container/Set-diagrams.png" width="400" /> |
| 33 | +</div> |
28 | 34 |
|
29 |
| -实际上 Set 就是 Collection,只是行为略有不同:Set 集合不允许包含相同的元素,如果试图把两个相同的元素加入同一个 Set 集合中,则添加操作失败,add() 方法返回 false,且新元素不会被加入。 |
| 35 | +- Set 继承了 Collection 的接口。实际上 Set 就是 Collection,只是行为略有不同:Set 集合不允许有重复元素。 |
| 36 | +- SortedSet 继承了 Set 的接口。SortedSet 中的内容是排序的唯一值,排序的方法是通过比较器(Comparator)。 |
| 37 | +- NavigableSet 继承了 SortedSet 的接口。相比于 NavigableSet 有一系列的导航方法;如"获取大于/等于某值的元素"、“获取小于/等于某值的元素”等等。 |
| 38 | +- AbstractSet 是一个抽象类,它继承于 AbstractCollection,AbstractCollection 实现了 Set 中的绝大部分函数,为 Set 的实现类提供了便利。 |
| 39 | +- HashSet 类依赖于 HashMap,它实际上是通过 HashMap 实现的。HashSet 中的元素是无序的。 |
| 40 | +- TreeSet 类依赖于 TreeMap,它实际上是通过 TreeMap 实现的。TreeSet 中的元素是有序的。 |
| 41 | +- LinkedHashSet 类具有 HashSet 的查找效率,且内部使用链表维护元素的插入顺序。 |
| 42 | +- EnumSet 中所有元素都必须是指定枚举类型的枚举值。 |
30 | 43 |
|
31 |
| -* HashSet:基于哈希实现,支持快速查找,但不支持有序性操作,例如根据一个范围查找元素的操作。并且失去了元素的插入顺序信息,也就是说使用 Iterator 遍历 HashSet 得到的结果是不确定的; |
| 44 | +## Set 接口 |
32 | 45 |
|
33 |
| -* TreeSet:基于红黑树实现,支持有序性操作,但是查找效率不如 HashSet,HashSet 查找时间复杂度为 O(1),TreeSet 则为 O(logN); |
| 46 | +Set 接口定义如下: |
34 | 47 |
|
35 |
| -* LinkedHashSet:具有 HashSet 的查找效率,且内部使用链表维护元素的插入顺序。 |
| 48 | +```java |
| 49 | +public interface Set<E> extends Collection<E> {} |
| 50 | +``` |
36 | 51 |
|
37 |
| -## HashSet |
| 52 | +Set 继承了 Collection 的接口。实际上,Set 就是 Collection,二者提供的方法完全相同。 |
38 | 53 |
|
39 |
| -### HashSet 要点 |
| 54 | +## SortedSet 接口 |
| 55 | + |
| 56 | +SortedSet 接口定义如下: |
40 | 57 |
|
41 |
| -HashSet 不保证元素的迭代访问顺序与插入顺序相同;并且元素的顺序随着时间推移可能发生改变。 |
| 58 | +```java |
| 59 | +public interface SortedSet<E> extends Set<E> {} |
| 60 | +``` |
42 | 61 |
|
43 |
| -HashSet 允许 null 值的元素。 |
| 62 | +SortedSet 接口新扩展的方法: |
44 | 63 |
|
45 |
| -HashSet 的基本操作(添加,删除,包含和大小)提供了恒定的时间性能,假设散列函数在桶之间正确地分散元素。迭代此集合需要的时间与HashSet实例的大小(元素数量)加上支持HashMap实例的“容量”(桶的数量)的总和成正比。因此,如果迭代性能很重要,不要将初始容量设置得太高(或者负载因子太低)是非常重要的。 |
| 64 | +- comparator - 返回 Comparator |
| 65 | +- subSet - 返回指定区间的子集 |
| 66 | +- headSet - 返回小于指定元素的子集 |
| 67 | +- tailSet - 返回大于指定元素的子集 |
| 68 | +- first - 返回第一个元素 |
| 69 | +- last - 返回最后一个元素 |
| 70 | +- spliterator |
46 | 71 |
|
47 |
| -HashSet 不是并发安全的,如果在并发状态下对其做迭代操作,会抛出 ConcurrentModificationException 异常。 |
| 72 | +## NavigableSet 接口 |
48 | 73 |
|
49 |
| -### HashSet 源码 |
| 74 | +NavigableSet 接口定义如下: |
| 75 | + |
| 76 | +```java |
| 77 | +public interface NavigableSet<E> extends SortedSet<E> {} |
| 78 | +``` |
| 79 | + |
| 80 | +NavigableSet 接口新扩展的方法: |
| 81 | + |
| 82 | +- lower - 返回小于指定值的元素中最接近的元素 |
| 83 | +- higher - 返回大于指定值的元素中最接近的元素 |
| 84 | +- floor - 返回小于或等于指定值的元素中最接近的元素 |
| 85 | +- ceiling - 返回大于或等于指定值的元素中最接近的元素 |
| 86 | +- pollFirst - 检索并移除第一个(最小的)元素 |
| 87 | +- pollLast - 检索并移除最后一个(最大的)元素 |
| 88 | +- descendingSet - 返回反序排列的 Set |
| 89 | +- descendingIterator - 返回反序排列的 Set 的迭代器 |
| 90 | +- subSet - 返回指定区间的子集 |
| 91 | +- headSet - 返回小于指定元素的子集 |
| 92 | +- tailSet - 返回大于指定元素的子集 |
| 93 | + |
| 94 | +## AbstractSet 抽象类 |
| 95 | + |
| 96 | +AbstractSet 抽象类定义如下: |
| 97 | + |
| 98 | +``` |
| 99 | +public abstract class AbstractSet<E> extends AbstractCollection<E> implements Set<E> {} |
| 100 | +``` |
| 101 | + |
| 102 | +AbstractSet 类提供 Set 接口的骨干实现,以最大限度地减少实现 Set 接口所需的工作。 |
| 103 | + |
| 104 | +事实上,主要的实现已经在 AbstractCollection 中完成。 |
| 105 | + |
| 106 | +## HashSet 类 |
| 107 | + |
| 108 | +HashSet 类定义如下: |
50 | 109 |
|
51 | 110 | ```java
|
52 | 111 | public class HashSet<E>
|
53 | 112 | extends AbstractSet<E>
|
54 |
| - implements Set<E>, Cloneable, java.io.Serializable { |
| 113 | + implements Set<E>, Cloneable, java.io.Serializable {} |
| 114 | +``` |
| 115 | + |
| 116 | +### HashSet 要点 |
| 117 | + |
| 118 | +1. HashSet 类通过继承 AbstractSet 实现了 Set 接口中的骨干方法。 |
| 119 | +2. HashSet 实现了 Cloneable,所以支持克隆。 |
| 120 | +3. HashSet 实现了 Serializable,所以支持序列化。 |
| 121 | +4. HashSet 中存储的元素是无序的。 |
| 122 | +5. HashSet 允许 null 值的元素。 |
| 123 | +6. HashSet 不是线程安全的。 |
55 | 124 |
|
56 |
| - // HashSet 的核心,通过维护一个 HashMap 实体来实现 HashSet 方法 |
57 |
| - private transient HashMap<E,Object> map; |
| 125 | +### HashSet 原理 |
58 | 126 |
|
59 |
| - // PRESENT 是用于关联 map 中当前操作元素的一个虚拟值 |
60 |
| - private static final Object PRESENT = new Object(); |
| 127 | +```java |
| 128 | +// HashSet 的核心,通过维护一个 HashMap 实体来实现 HashSet 方法 |
| 129 | +private transient HashMap<E,Object> map; |
| 130 | + |
| 131 | +// PRESENT 是用于关联 map 中当前操作元素的一个虚拟值 |
| 132 | +private static final Object PRESENT = new Object(); |
61 | 133 | }
|
62 | 134 | ```
|
63 | 135 |
|
64 |
| -HashSet 中维护了一个 HashMap 对象 map,它是 HashSet 的核心。实际上,HashSet 的重要方法,如 add、remove、iterator、clear、size 等都是围绕 map 实现的。所以,可以说,HashSet 的实现本质上依赖于 HashMap。 |
| 136 | +**HashSet 是基于 HashMap 实现的。** |
| 137 | + |
| 138 | +HashSet 中维护了一个 HashMap 对象 map,HashSet 的重要方法,如 add、remove、iterator、clear、size 等都是围绕 map 实现的。 |
65 | 139 |
|
66 | 140 | PRESENT 是用于关联 map 中当前操作元素的一个虚拟值。
|
67 | 141 |
|
68 |
| -## TreeSet |
| 142 | +HashSet 类中通过定义 `writeObject()` 和 `readObject()` 方法确定了其序列化和反序列化的机制。 |
69 | 143 |
|
70 |
| -### TreeSet 要点 |
| 144 | +## TreeSet 类 |
71 | 145 |
|
72 |
| -基于 TreeMap 的 NavigableSet 实现。这些元素使用它们的自然顺序或者在创建集合时提供的比较器进行排序,具体取决于使用哪个构造函数。 |
| 146 | +TreeSet 类定义如下: |
| 147 | + |
| 148 | +```java |
| 149 | +public class TreeSet<E> extends AbstractSet<E> |
| 150 | + implements NavigableSet<E>, Cloneable, java.io.Serializable {} |
| 151 | +``` |
73 | 152 |
|
74 |
| -TreeSet 会对元素进行排序,排序规则是自然顺序或比较器(Comparator)中提供的顺序规则。 |
| 153 | +### TreeSet 要点 |
75 | 154 |
|
76 |
| -TreeSet 不是并发安全的。如果在并发环境下使用,需要在外部调用处保证同步。 |
| 155 | +1. TreeSet 类通过继承 AbstractSet 实现了 NavigableSet 接口中的骨干方法。 |
| 156 | +2. TreeSet 实现了 Cloneable,所以支持克隆。 |
| 157 | +3. TreeSet 实现了 Serializable,所以支持序列化。 |
| 158 | +4. TreeSet 中存储的元素是有序的。排序规则是自然顺序或比较器(Comparator)中提供的顺序规则。 |
| 159 | +5. TreeSet 不是线程安全的。 |
77 | 160 |
|
78 | 161 | ### TreeSet 源码
|
79 | 162 |
|
80 | 163 | ```java
|
81 |
| -public class TreeSet<E> extends AbstractSet<E> |
82 |
| - implements NavigableSet<E>, Cloneable, java.io.Serializable |
83 |
| -{ |
84 |
| - // TreeSet 的核心,通过维护一个 NavigableMap 实体来实现 TreeSet 方法 |
85 |
| - private transient NavigableMap<E,Object> m; |
| 164 | +// TreeSet 的核心,通过维护一个 NavigableMap 实体来实现 TreeSet 方法 |
| 165 | +private transient NavigableMap<E,Object> m; |
86 | 166 |
|
87 |
| - // PRESENT 是用于关联 map 中当前操作元素的一个虚拟值 |
88 |
| - private static final Object PRESENT = new Object(); |
| 167 | +// PRESENT 是用于关联 map 中当前操作元素的一个虚拟值 |
| 168 | +private static final Object PRESENT = new Object(); |
89 | 169 | ```
|
90 | 170 |
|
91 |
| -## EnumSet |
| 171 | +**TreeSet 是基于 TreeMap 实现的。** |
92 | 172 |
|
93 |
| -### EnumSet 要点 |
| 173 | +TreeSet 中维护了一个 NavigableMap 对象 map(实际上是一个 TreeMap 实例),TreeSet 的重要方法,如 add、remove、iterator、clear、size 等都是围绕 map 实现的。 |
| 174 | + |
| 175 | +PRESENT 是用于关联 map 中当前操作元素的一个虚拟值。TreeSet 中的元素都被当成 TreeMap 的 key 存储,而 value 都填的是 PRESENT。 |
| 176 | + |
| 177 | +## LinkedHashSet 类 |
| 178 | + |
| 179 | +LinkedHashSet 类定义如下: |
| 180 | + |
| 181 | +```java |
| 182 | +public class LinkedHashSet<E> |
| 183 | + extends HashSet<E> |
| 184 | + implements Set<E>, Cloneable, java.io.Serializable {} |
| 185 | +``` |
| 186 | + |
| 187 | +### LinkedHashSet 要点 |
| 188 | + |
| 189 | +1. LinkedHashSet 类通过继承 HashSet 实现了 Set 接口中的骨干方法。 |
| 190 | +2. LinkedHashSet 实现了 Cloneable,所以支持克隆。 |
| 191 | +3. LinkedHashSet 实现了 Serializable,所以支持序列化。 |
| 192 | +4. LinkedHashSet 中存储的元素是按照插入顺序保存的。 |
| 193 | +5. LinkedHashSet 不是线程安全的。 |
94 | 194 |
|
95 |
| -EnumSet 是用于枚举类型的 Set 实现。 |
| 195 | +### LinkedHashSet 原理 |
96 | 196 |
|
97 |
| -EnumSet 不允许加入null元素。EnumSet 中的所有元素必须来自单个枚举类型,该集合类型在创建集合时显式或隐式指定。 |
| 197 | +LinkedHashSet 有三个构造方法,无一例外,都是调用父类 HashSet 的构造方法。 |
98 | 198 |
|
99 |
| -EnumSet 没有构造方法,只能通过类中的 static 方法来创建 EnumSet 对象。 |
| 199 | +```java |
| 200 | +public LinkedHashSet(int initialCapacity, float loadFactor) { |
| 201 | + super(initialCapacity, loadFactor, true); |
| 202 | +} |
| 203 | +public LinkedHashSet(int initialCapacity) { |
| 204 | + super(initialCapacity, .75f, true); |
| 205 | +} |
| 206 | +public LinkedHashSet() { |
| 207 | + super(16, .75f, true); |
| 208 | +} |
| 209 | +``` |
| 210 | + |
| 211 | +需要强调的是:**LinkedHashSet 构造方法实际上调用的是父类 HashSet 的非 public 构造方法。** |
| 212 | + |
| 213 | +```java |
| 214 | +HashSet(int initialCapacity, float loadFactor, boolean dummy) { |
| 215 | + map = new LinkedHashMap<>(initialCapacity, loadFactor); |
| 216 | +} |
| 217 | +``` |
| 218 | + |
| 219 | +不同于 HashSet public 构造方法中初始化的 HashMap 实例,这个构造方法中,初始化了 LinkedHashMap 实例。 |
| 220 | + |
| 221 | +也就是说,实际上,LinkedHashSet 维护了一个双链表。由双链表的特性可以知道,它是按照元素的插入顺序保存的。所以,这就是 LinkedHashSet 中存储的元素是按照插入顺序保存的原理。 |
| 222 | + |
| 223 | +## EnumSet 类 |
| 224 | + |
| 225 | +EnumSet 类定义如下: |
| 226 | + |
| 227 | +```java |
| 228 | +public abstract class EnumSet<E extends Enum<E>> extends AbstractSet<E> |
| 229 | + implements Cloneable, java.io.Serializable {} |
| 230 | +``` |
| 231 | + |
| 232 | +### EnumSet 要点 |
100 | 233 |
|
101 |
| -EnumSet 是有序的。以枚举值在 EnumSet 类中的定义顺序来决定集合元素的顺序。 |
| 234 | +1. EnumSet 类继承了 AbstractSet,所以有 Set 接口中的骨干方法。 |
| 235 | +2. EnumSet 实现了 Cloneable,所以支持克隆。 |
| 236 | +3. EnumSet 实现了 Serializable,所以支持序列化。 |
| 237 | +4. EnumSet 通过 `<E extends Enum<E>>` 限定了存储元素必须是枚举值。 |
| 238 | +5. EnumSet 没有构造方法,只能通过类中的 static 方法来创建 EnumSet 对象。 |
| 239 | +6. EnumSet 是有序的。以枚举值在 EnumSet 类中的定义顺序来决定集合元素的顺序。 |
| 240 | +7. EnumSet 不是线程安全的。 |
102 | 241 |
|
103 |
| -EnumSet 不是并发安全的。 |
| 242 | +## 资料 |
104 | 243 |
|
| 244 | +- [Java 编程思想(Thinking in java)](https://item.jd.com/10058164.html) |
0 commit comments