Set 是 java.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
带集合参数的构造函数:
HashSet(Collection extends E> c):创建一个新的HashSet,其元素来自于指定的集合c。新创建的HashSet不会包含任何重复的元素,即便原始集合中有重复项
// 创建一个 List 集合
List
// 创建一个 HashSet 集合,传入list集合参数
HashSet
System.out.println(set); // 输出: [Apple, Cherry, Banana]
指定初始容量的构造方法:
HashSet(int initialCapacity):创建一个新的、空的HashSet,其初始容量被设定为指定的数值。负载因子依然默认为0.75。
// 初始化容量为100的 HashSet 集合
HashSet
指定初始容量和负载因子的构造函数:
HashSet(int initialCapacity, float loadFactor):创建一个新的、空的HashSet,允许同时指定初始容量和负载因子。初始容量是你预估的最大元素数量,负载因子是用于确定何时重新调整HashMap容量的阈值,当已存储元素数量超过容量与负载因子的乘积时,HashMap会自动扩容。
// 初始容量为100,负载因子为0.8 的 HashSet 集合
HashSet
注意:
初始容量(initialCapacity)必须是大于0的整数,否则会抛出IllegalArgumentException异常。
负载因子(loadFactor)必须是大于0且不大于1的浮点数,若传入负数或大于等于1的数也会抛出IllegalArgumentException异常。
设置适当的初始容量和负载因子可以帮助优化性能,减少扩容操作带来的开销。过低的初始容量会导致频繁扩容,过高则可能浪费空间。一般来说,负载因子较小意味着更早触发扩容,从而降低冲突概率,但也意味着更高的空间消耗。
示例代码
// 创建一个HashSet实例
Set
// 添加元素
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
// 自动按照字符串字典顺序排序
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
@Override
public int compare(Person p1, Person p2) {
// 按照年龄排序,年龄小的在前
return Integer.compare(p1.getAge(), p2.getAge());
}
};
// 使用自定义的比较器创建TreeSet
TreeSet
// 添加一些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
// 创建一个 TreeSet 实例
TreeSet
System.out.println(treeSet);// 输出: [Apple, Banana, Cherry]
特有方法
first(): 返回此集合中的第一个(按自然排序顺序或根据构造集合时提供的 Comparator)元素的返回值。
public E first();
last(): 返回此集合中的最后一个元素,与 first() 方法相反。
public E last();
headSet(E toElement): 返回一个从集合的第一个元素到指定元素(不包括)的视图。
public SortedSet
tailSet(E fromElement): 返回一个从指定元素(包括)到集合的最后一个元素的视图。
public SortedSet
subSet(E fromElement, E toElement): 返回一个从 fromElement(包括)到 toElement(不包括)的视图。
public SortedSet
descendingIterator(): 返回一个按照降序遍历集合的迭代器。
public Iterator
descendingSet(): 返回一个按照降序排列的此集合的视图。
public NavigableSet
代码示例:
public static void main(String[] args) {
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 集合是一种哈希表和链表的组合,它具有以下特点:
无序性:与 HashSet 类似,LinkedHashSet 也不允许集合中有重复的元素。
有序性:与 HashSet 不同的是,LinkedHashSet 维护了一个双向链表,使得迭代它时可以按照插入顺序访问集合中的元素。
性能:LinkedHashSet 在大多数情况下提供与 HashSet 相同的时间和空间复杂度,即添加、删除和查找元素的时间复杂度为 O(1)。
线程不安全:与 HashSet 一样,LinkedHashSet 也是线程不安全的。如果需要线程安全的集合,可以使用 Collections.synchronizedSet(new LinkedHashSet<>()) 包装一个 LinkedHashSet,或者使用 ConcurrentHashMap 作为底层实现。
构造函数:LinkedHashSet 提供了多种构造函数,允许你指定初始容量和加载因子,或者使用另一个集合的元素来创建 LinkedHashSet。
LinkedHashSet 的使用场景
当你需要一个不允许重复元素的集合,并且希望迭代时能够按照元素的插入顺序进行时,可以使用 LinkedHashSet。
LinkedHashSet 可以作为 HashMap 的键集合,因为它提供了快速的查找和迭代性能。