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提供了两种类型的迭代器:IteratorSpliterator**。

前者只能遍历并执行对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移除元素的另一种选择。Iteratorremove()方法会移除当前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上找到。