1. 概述
在这篇文章中,我们将探讨Java Collections API中的LinkedHashSet
类。我们将深入研究这种数据结构的特点,并演示其功能。
2. LinkedHashSet
简介
LinkedHashSet
是属于Java.util
库的泛型数据结构,它是HashSet
数据结构的直接子类,因此在任何时候都包含不重复的元素。
除了在其所有条目中维护一个双向链表外,它的实现与HashSet
不同,它保持了一个可预测的迭代顺序。这个迭代顺序由元素添加到集合的顺序定义。
通过使用LinkedHashSet
,客户端可以避免HashSet
提供的不可预测的排序方式,而不会带来TreeSet
的复杂性。
虽然由于需要维护链表,其性能可能略逊于HashSet
,但对于add()
、contains()
和remove()
操作,它的性能保持常数时间(O1)。
3. 创建LinkedHashSet
创建LinkedHashSet
有多种方法,让我们一一来看:
3.1. 默认无参构造函数
Set<String> linkedHashSet = new LinkedHashSet<>();
assertTrue(linkedHashSet.isEmpty());
3.2. 初始容量创建
初始容量代表LinkedHashSet
的初始长度。提供初始容量可以防止在集合增长时进行不必要的扩容。默认初始容量为16:
LinkedHashSet<String> linkedHashSet = new LinkedHashSet<>(20);
3.3. 从Collection
创建
我们也可以在创建LinkedHashSet
对象时,使用Collection
的内容来填充:
@Test
void whenCreatingLinkedHashSetWithExistingCollection_shouldContainAllElementOfCollection(){
Collection<String> data = Arrays.asList("first", "second", "third", "fourth", "fifth");
LinkedHashSet<String> linkedHashSet = new LinkedHashSet<>(data);
assertFalse(linkedHashSet.isEmpty());
assertEquals(data.size(), linkedHashSet.size());
assertTrue(linkedHashSet.containsAll(data) && data.containsAll(linkedHashSet));
}
3.4. 使用初始容量和负载因子创建
当LinkedHashSet
的大小超过初始容量时,新的容量将是负载因子乘以前的容量。以下代码片段将初始容量设置为20,负载因子设为3。
LinkedHashSet<String> linkedHashSet = new LinkedHashSet<>(20, 3);
默认的负载因子为0.75。
4. 向LinkedHashSet
添加元素
我们可以使用add()
和addAll()
方法分别向LinkedHashSet
添加单个元素或元素集合。如果元素尚未存在于集合中,将会被添加。这些方法在元素添加到集合时返回true
,否则返回false
。
4.1. 添加单个元素
以下是向LinkedHashSet
添加元素的实现:
@Test
void whenAddingElement_shouldAddElement(){
Set<Integer> linkedHashSet = new LinkedHashSet<>();
assertTrue(linkedHashSet.add(0));
assertFalse(linkedHashSet.add(0));
assertTrue(linkedHashSet.contains(0));
}
4.2. 添加元素集合
如前所述,我们也可以向LinkedHashSet
添加元素集合:
@Test
void whenAddingCollection_shouldAddAllContentOfCollection(){
Collection<Integer> data = Arrays.asList(1,2,3);
LinkedHashSet<Integer> linkedHashSet = new LinkedHashSet<>();
assertTrue(linkedHashSet.addAll(data));
assertTrue(data.containsAll(linkedHashSet) && linkedHashSet.containsAll(data));
}
addAll()
方法同样遵循不添加重复元素的规则,如下所示:
@Test
void whenAddingCollectionWithDuplicateElements_shouldMaintainUniqueValuesInSet(){
LinkedHashSet<Integer> linkedHashSet = new LinkedHashSet<>();
linkedHashSet.add(2);
Collection<Integer> data = Arrays.asList(1, 1, 2, 3);
assertTrue(linkedHashSet.addAll(data));
assertEquals(3, linkedHashSet.size());
assertTrue(data.containsAll(linkedHashSet) && linkedHashSet.containsAll(data));
}
请注意,data
变量包含重复的值1,而在调用addAll()
方法之前,LinkedHashSet
已经包含了整数值2。
5. 遍历LinkedHashSet
作为Collection
库的每个后代,我们可以遍历LinkedHashSet
。**LinkedHashSet
提供了两种类型的迭代器:Iterator
和Spliterator
**。
前者只能遍历并执行对Collection
的基本操作,而后者将Collection
分割成子集,并在每个子集上并行执行不同的操作,从而使其线程安全。
5.1. 使用Iterator
遍历
@Test
void whenIteratingWithIterator_assertThatElementIsPresent(){
LinkedHashSet<Integer> linkedHashSet = new LinkedHashSet<>();
linkedHashSet.add(0);
linkedHashSet.add(1);
linkedHashSet.add(2);
Iterator<Integer> iterator = linkedHashSet.iterator();
for (int i = 0; i < linkedHashSet.size(); i++) {
int nextData = iterator.next();
assertEquals(i, nextData);
}
}
5.2. 使用Spliterator
遍历
@Test
void whenIteratingWithSpliterator_assertThatElementIsPresent(){
LinkedHashSet<Integer> linkedHashSet = new LinkedHashSet<>();
linkedHashSet.add(0);
linkedHashSet.add(1);
linkedHashSet.add(2);
Spliterator<Integer> spliterator = linkedHashSet.spliterator();
AtomicInteger counter = new AtomicInteger();
spliterator.forEachRemaining(data -> {
assertEquals(counter.get(), (int)data);
counter.getAndIncrement();
});
}
6. 从LinkedHashSet
移除元素
以下是移除LinkedHashSet
元素的不同方法:
6.1. remove()
此方法根据我们想要移除的确切元素从集合中移除元素。它接受要移除的实际元素作为参数,并在成功移除时返回true
,否则返回false
:
@Test
void whenRemovingAnElement_shouldRemoveElement(){
Collection<String> data = Arrays.asList("first", "second", "third", "fourth", "fifth");
LinkedHashSet<String> linkedHashSet = new LinkedHashSet<>(data);
assertTrue(linkedHashSet.remove("second"));
assertFalse(linkedHashSet.contains("second"));
}
6.2. removeIf()
removeIf()
方法移除满足指定谓词条件的元素。下面的示例删除LinkedHashSet
中所有大于2的元素:
@Test
void whenRemovingAnElementGreaterThanTwo_shouldRemoveElement(){
LinkedHashSet<Integer> linkedHashSet = new LinkedHashSet<>();
linkedHashSet.add(0);
linkedHashSet.add(1);
linkedHashSet.add(2);
linkedHashSet.add(3);
linkedHashSet.add(4);
linkedHashSet.removeIf(data -> data > 2);
assertFalse(linkedHashSet.contains(3));
assertFalse(linkedHashSet.contains(4));
}
6.3. 使用Iterator
移除
Iterator
也是我们可以用来从LinkedHashSet
移除元素的另一种选择。Iterator
的remove()
方法会移除当前Iterator
所指向的元素:
@Test
void whenRemovingAnElementWithIterator_shouldRemoveElement(){
LinkedHashSet<Integer> linkedHashSet = new LinkedHashSet<>();
linkedHashSet.add(0);
linkedHashSet.add(1);
linkedHashSet.add(2);
Iterator<Integer> iterator = linkedHashSet.iterator();
int elementToRemove = 1;
assertTrue(linkedHashSet.contains(elementToRemove));
while(iterator.hasNext()){
if(elementToRemove == iterator.next()){
iterator.remove();
}
}
assertFalse(linkedHashSet.contains(elementToRemove));
}
7. 结论
在这篇文章中,我们研究了Java Collections库中的LinkedHashSet
数据结构。我们展示了如何通过不同的构造函数创建LinkedHashSet
,添加和移除元素,以及遍历它。我们还了解了这种数据结构的底层原理,它相对于HashSet
的优势,以及常用操作的时间复杂度。
如往常一样,代码示例可以在GitHub上找到。