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会自动调整容量以容纳更多的元素。
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
)元素的返回值。csharppublic E first();
-
last() : 返回此集合中的最后一个元素,与
first()
方法相反。csharppublic E last();
-
headSet(E toElement) : 返回一个从集合的第一个元素到指定元素(不包括)的视图。
arduinopublic SortedSet<E> headSet(E toElement);
-
tailSet(E fromElement) : 返回一个从指定元素(包括)到集合的最后一个元素的视图。
arduinopublic SortedSet<E> tailSet(E fromElement);
-
subSet(E fromElement, E toElement) : 返回一个从
fromElement
(包括)到toElement
(不包括)的视图。arduinopublic SortedSet<E> subSet(E fromElement, E toElement);
-
descendingIterator() : 返回一个按照降序遍历集合的迭代器。
csharppublic Iterator<E> descendingIterator();
-
descendingSet() : 返回一个按照降序排列的此集合的视图。
csharppublic 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
集合是一种哈希表和链表的组合,它具有以下特点:
- 无序性 :与
HashSet
类似,LinkedHashSet
也不允许集合中有重复的元素。 - 有序性 :与
HashSet
不同的是,LinkedHashSet
维护了一个双向链表,使得迭代它时可以按照插入顺序访问集合中的元素。 - 性能 :
LinkedHashSet
在大多数情况下提供与HashSet
相同的时间和空间复杂度,即添加、删除和查找元素的时间复杂度为 O(1)。 - 线程不安全 :与
HashSet
一样,LinkedHashSet
也是线程不安全的。如果需要线程安全的集合,可以使用Collections.synchronizedSet(new LinkedHashSet<>())
包装一个LinkedHashSet
,或者使用ConcurrentHashMap
作为底层实现。 - 构造函数 :
LinkedHashSet
提供了多种构造函数,允许你指定初始容量和加载因子,或者使用另一个集合的元素来创建LinkedHashSet
。
LinkedHashSet 的使用场景
- 当你需要一个不允许重复元素的集合,并且希望迭代时能够按照元素的插入顺序进行时,可以使用
LinkedHashSet
。 LinkedHashSet
可以作为HashMap
的键集合,因为它提供了快速的查找和迭代性能。