JAVA 集合框架(三) Set集合详解和常用方法

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会自动调整容量以容纳更多的元素。
arduino 复制代码
// 创建一个 空的 HashSet 集合
HashSet<String> set = new HashSet<>();

带集合参数的构造函数

  • HashSet(Collection<? extends E> c):创建一个新的HashSet,其元素来自于指定的集合c。新创建的HashSet不会包含任何重复的元素,即便原始集合中有重复项
arduino 复制代码
// 创建一个 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。
arduino 复制代码
// 初始化容量为100的 HashSet 集合
HashSet<String> set = new HashSet<>(100); 

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

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

注意

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

示例代码

csharp 复制代码
// 创建一个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接口。
csharp 复制代码
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进行排序。
csharp 复制代码
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,元素按照其自然顺序排序。
ini 复制代码
// 创建一个 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)元素的返回值。

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

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

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

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

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

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

    csharp 复制代码
    public NavigableSet<E> descendingSet();

代码示例:

csharp 复制代码
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 的键集合,因为它提供了快速的查找和迭代性能。
相关推荐
bill44716 小时前
BPMN2.0,flowable工作流,【用户任务】使用【任务监听器】动态设置下一步处理人
java·工作流引擎·flowable·bpmn
Cricyta Sevina16 小时前
Java 语言多线程核心概念全解析
java·开发语言
shenzhenNBA16 小时前
如何在python文件中使用日志功能?简单版本
java·前端·python·日志·log
遇印记16 小时前
javaOCA考点(基础)
java·开发语言·青少年编程
阿里云云原生16 小时前
告别“看不见的内存”!SysOM 如何实现 Java 进程内存全景分析?
java·云原生
Andy工程师16 小时前
Spring Boot 按照以下顺序加载配置(后面的会覆盖前面的):
java·spring boot·后端
繁星蓝雨16 小时前
小试Spring boot项目程序(进行get、post方法、打包运行)——————附带详细代码与示例
java·spring boot·后端
加藤不太惠16 小时前
【无标题】
java·数据结构·算法