掘金 后端 ( ) • 2024-04-19 11:12

Setjava.util 包下集合框架中一个接口,它是 Collection 接口的一个子接口,表示不允许包含重复元素的集合。Set 集合的特点是集合内的元素无序,且每个元素都是唯一的。这意味着即使试图添加两个相等的对象(依据 .equals() 方法判断相等),Set 集合只会保存一个对象。

Set集合的特点

  • 无序性:Set 集合中的元素不按任何特定顺序排列,无法通过索引访问元素,即集合内部的元素顺序可能随时间和操作发生变化。
  • 唯一性:Set 集合不允许包含重复的元素。判断元素是否重复的标准是基于元素的 .equals() 方法。如果两个对象在 .equals() 方法下判断为相等,则 Set 集合中只会存储其中一个。
  • 最大容量:理论上,Set 集合可以无限增长,直到受到可用内存限制为止。

Set集合的主要实现类

  • HashSet:基于哈希表实现,具有良好的插入、删除和查找性能(平均时间复杂度为 O(1)),但不保证元素的迭代顺序。
  • TreeSet:基于红黑树实现,元素自动排序(要么基于元素自身的自然排序,要么通过自定义 Comparator),插入、删除和查找性能为 O(log n),确保了集合内元素的排序顺序。
  • LinkedHashSet:结合了 HashSet 和 LinkedList 的特性,它维护元素插入的顺序,同时仍然保证元素的唯一性。

HashSet

HashSet是Java集合框架中一个实现Set接口的类,它使用哈希表(内部一般采用HashMap)作为底层数据结构,主要用于存储不重复的元素集合。

HashSet集合有以下特点:

  • 无序性
  • 唯一性
  • 高效性:由于基于哈希表实现,HashSet插入、删除和查找元素的平均时间复杂度为O(1),前提是哈希函数能够良好地分散冲突。
  • 允许存储null值:HashSet允许存储一个null元素,但仅能存储一个,因为null的哈希码固定为0。

构造函数

HashSet类在Java集合框架中提供了多个构造函数,用于创建不同的HashSet实例。以下是HashSet的主要构造函数及其详细讲解:

无参数构造函数

  • HashSet():此构造函数创建一个新的、空的HashSet实例。底层HashMap的初始容量为16,负载因子为0.75。随着元素的添加,HashSet会自动调整容量以容纳更多的元素。
// 创建一个 空的 HashSet 集合
HashSet<String> set = new HashSet<>();

带集合参数的构造函数

  • HashSet(Collection<? extends E> c):创建一个新的HashSet,其元素来自于指定的集合c。新创建的HashSet不会包含任何重复的元素,即便原始集合中有重复项
// 创建一个 List 集合
List<String> list = Arrays.asList("Apple", "Banana", "Cherry", "Apple");
// 创建一个 HashSet 集合,传入list集合参数
HashSet<String> set = new HashSet<>(list);
System.out.println(set); // 输出: [Apple, Cherry, Banana]

指定初始容量的构造方法

  • HashSet(int initialCapacity):创建一个新的、空的HashSet,其初始容量被设定为指定的数值。负载因子依然默认为0.75。
// 初始化容量为100的 HashSet 集合
HashSet<String> set = new HashSet<>(100); 

指定初始容量和负载因子的构造函数

  • HashSet(int initialCapacity, float loadFactor):创建一个新的、空的HashSet,允许同时指定初始容量和负载因子。初始容量是你预估的最大元素数量,负载因子是用于确定何时重新调整HashMap容量的阈值,当已存储元素数量超过容量与负载因子的乘积时,HashMap会自动扩容。
// 初始容量为100,负载因子为0.8 的 HashSet 集合
HashSet<String> set = new HashSet<>(100, 0.8f); 

注意

  • 初始容量(initialCapacity)必须是大于0的整数,否则会抛出IllegalArgumentException异常。
  • 负载因子(loadFactor)必须是大于0且不大于1的浮点数,若传入负数或大于等于1的数也会抛出IllegalArgumentException异常。
  • 设置适当的初始容量和负载因子可以帮助优化性能,减少扩容操作带来的开销。过低的初始容量会导致频繁扩容,过高则可能浪费空间。一般来说,负载因子较小意味着更早触发扩容,从而降低冲突概率,但也意味着更高的空间消耗。

示例代码

// 创建一个HashSet实例
Set<String> list = new HashSet<>();
​
// 添加元素
list.add("Apple");
list.add("Banana");
list.add("Cherry");
// 尝试添加重复元素,不会添加成功
list.add("Apple");
​
// 输出HashSet中的元素
System.out.println(list);
​
// 检查元素是否存在
System.out.println("'Banana'是否存在? " + list.contains("Banana"));
​
// 删除元素
list.remove("Banana");
System.out.println("删除'Banana'后: " + list);
​
// 清空HashSet
list.clear();
System.out.println("清空操作后: " + list);

TreeSet

TreeSet是Java集合框架中实现Set接口的一个重要类,它基于红黑树(Red-Black Tree)数据结构,提供了一个有序的、不包含重复元素的集合

TreeSet集合有以下特点:

  • 唯一性
  • 有序性TreeSet中的元素是有序的,排序规则既可以是元素本身的自然排序(元素类实现了Comparable接口),也可以是由客户端提供的Comparator来决定。
  • 自平衡:由于基于红黑树实现,TreeSet在插入、删除和查找操作后都能保持树的平衡,从而确保这些操作的时间复杂度接近O(log n)。

构造函数

无参数构造函数

  • TreeSet():创建一个空的TreeSet,其中的元素将按照它们的自然顺序排序。这意味着元素类型需要实现Comparable接口。
TreeSet<String> treeSet = new TreeSet<>();
// 自动按照字符串字典顺序排序
treeSet.add("Apple");
treeSet.add("Banana");
treeSet.add("Cherry");
System.out.println(treeSet);// 输出: [Apple, Banana, Cherry]

使用Comparator构造函数

  • TreeSet(Comparator<? super E> comparator):创建一个空的TreeSet,其中的元素将按照指定的Comparator进行排序。
public class Demo05_TreeSet {
    public static void main(String[] args) {
        // 定义一个自定义的比较器
        Comparator<Person> comparator = new Comparator<Person>() {
            @Override
            public int compare(Person p1, Person p2) {
                // 按照年龄排序,年龄小的在前
                return Integer.compare(p1.getAge(), p2.getAge());
            }
        };
​
        // 使用自定义的比较器创建TreeSet
        TreeSet<Person> peopleByAge = new TreeSet<>(comparator);
​
        // 添加一些Person对象
        peopleByAge.add(new Person("Alice", 30));
        peopleByAge.add(new Person("Bob", 25));
        peopleByAge.add(new Person("Charlie", 35));
​
        // 输出排序后的Person对象
        for (Person person : peopleByAge) {
            System.out.println(person.getName() + ": " + person.getAge());
        }
    }
​
    // 简单的Person类示例
    public static class Person {
        private String name;
        private int age;
​
        public Person(String name, int age) {
            this.name = name;
            this.age = age;
        }
​
        public String getName() {
            return name;
        }
​
        public int getAge() {
            return age;
        }
    }
}

包含集合元素的构造函数

  • TreeSet(Collection<? extends E> c):创建一个包含指定集合元素的新TreeSet,元素按照其自然顺序排序。
// 创建一个 List 集合
List<String> list = Arrays.asList("Banana", "Apple", "Cherry");
// 创建一个 TreeSet 实例
TreeSet<String> treeSet = new TreeSet<>(list);
System.out.println(treeSet);// 输出: [Apple, Banana, Cherry]

特有方法

  • first() : 返回此集合中的第一个(按自然排序顺序或根据构造集合时提供的 Comparator)元素的返回值。

    public E first();
    
  • last() : 返回此集合中的最后一个元素,与 first() 方法相反。

    public E last();
    
  • headSet(E toElement) : 返回一个从集合的第一个元素到指定元素(不包括)的视图。

    public SortedSet<E> headSet(E toElement);
    
  • tailSet(E fromElement) : 返回一个从指定元素(包括)到集合的最后一个元素的视图。

    public SortedSet<E> tailSet(E fromElement);
    
  • subSet(E fromElement, E toElement) : 返回一个从 fromElement(包括)到 toElement(不包括)的视图。

    public SortedSet<E> subSet(E fromElement, E toElement);
    
  • descendingIterator() : 返回一个按照降序遍历集合的迭代器。

    public Iterator<E> descendingIterator();
    
  • descendingSet() : 返回一个按照降序排列的此集合的视图。

    public NavigableSet<E> descendingSet();
    

代码示例:

public static void main(String[] args) {
        TreeSet<Integer> treeSet = new TreeSet<>();
        treeSet.add(10);
        treeSet.add(20);
        treeSet.add(30);
        treeSet.add(40);
​
        // 使用 first() 和 last() 方法
        System.out.println("第一个元素: " + treeSet.first());
        System.out.println("最后一个元素: " + treeSet.last());
​
        // 使用 headSet() 方法
        System.out.println("Head set to 30: " + treeSet.headSet(30));
​
        // 使用 tailSet() 方法
        System.out.println("Tail set from 30: " + treeSet.tailSet(30));
​
        // 使用 subSet() 方法
        System.out.println("Sub set from 10 to 30: " + treeSet.subSet(10, 30));
​
        // 使用 descendingIterator() 方法
        System.out.println("Descending iterator:");
        treeSet.descendingIterator().forEachRemaining(System.out::println);
}

LinkedHashSet

LinkedHashSet 是 Java 集合框架中的一个类,它继承自 HashSet 并实现了 Set 接口。LinkedHashSet 集合是一种哈希表和链表的组合,它具有以下特点:

  1. 无序性:与 HashSet 类似,LinkedHashSet 也不允许集合中有重复的元素。
  2. 有序性:与 HashSet 不同的是,LinkedHashSet 维护了一个双向链表,使得迭代它时可以按照插入顺序访问集合中的元素。
  3. 性能LinkedHashSet 在大多数情况下提供与 HashSet 相同的时间和空间复杂度,即添加、删除和查找元素的时间复杂度为 O(1)。
  4. 线程不安全:与 HashSet 一样,LinkedHashSet 也是线程不安全的。如果需要线程安全的集合,可以使用 Collections.synchronizedSet(new LinkedHashSet<>()) 包装一个 LinkedHashSet,或者使用 ConcurrentHashMap 作为底层实现。
  5. 构造函数LinkedHashSet 提供了多种构造函数,允许你指定初始容量和加载因子,或者使用另一个集合的元素来创建 LinkedHashSet

LinkedHashSet 的使用场景

  • 当你需要一个不允许重复元素的集合,并且希望迭代时能够按照元素的插入顺序进行时,可以使用 LinkedHashSet
  • LinkedHashSet 可以作为 HashMap 的键集合,因为它提供了快速的查找和迭代性能。