在 Java 中,Set 是一个接口,表示一组不允许重复的元素。它继承自 Collection 接口。
主要实现类:
HashSet,LinkedHashSet,TreeSet
HashSet
HashSet 是基于 HashMap 实现的,其底层原理主要依赖于哈希表。
工作原理
哈希表结构:
使用哈希表(数组加链表/红黑树)来存储元素。
元素通过计算哈希值找到在表中的位置。
添加元素:
计算元素的哈希值,找到对应的桶位置。
如果该位置没有元素,则直接存储。
如果已有元素,使用链表(或红黑树)解决哈希冲突。
哈希冲突:
当多个元素的哈希值相同时,使用链表存储冲突的元素。
在 Java 8 之后,当链表长度超过阈值时,转换为红黑树,提高查找效率。
查找和删除:
通过哈希值快速定位到元素所在的桶。
在桶中遍历链表或红黑树,找到目标元素进行操作。
扩容机制:
当元素数量超过负载因子(默认0.75)乘以当前容量时,HashSet 会扩容。
扩容时,重新计算所有元素的哈希值并分配到新的桶中。
特点
无序:元素插入顺序不被保证。
不允许重复:元素根据哈希值存储,重复元素会被覆盖。
高效操作:add、remove、contains 的时间复杂度为 O(1)(平均情况下)。
HashSet 适用于需要快速判断元素存在性和去重的场景,可以存储一个 null 元素。
public class test {
public static void main(String[] args) {
Set<Student> s = new HashSet<>();
s.add(new Student("张三",32));
s.add(new Student("李武", 23));
s.add(new Student("张三",32));
s.add(new Student("王阳", 44));
System.out.println(s);//两个相同的张三全部输出
//原因是两个对象的哈希值不同
}
}
重写hashCode()和equls()函数可以合并两个属性值完全相同的对象。
//属性相同即返回true
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age && Objects.equals(name, student.name);
}
// 根据属性值生成哈希值
@Override
public int hashCode() {
return Objects.hash(name, age);
}
LinkedHashSet
LinkedHashSet 是 Java 集合框架中的一个类,继承自 HashSet,并通过维护一个双向链表来记录元素的插入顺序。
底层原理
基于 HashMap 实现:
和 HashSet 一样,LinkedHashSet 的底层使用 HashMap 来存储元素。
维护插入顺序:
通过一个双向链表记录元素的插入顺序。
每个元素在哈希表中的节点同时包含了链表中的指针,用于维护顺序。
哈希表和链表结合:
元素存储在哈希表中以实现快速访问。
链表维护元素的插入顺序。
操作效率:
插入、删除和查找的时间复杂度为 O(1)(平均情况下)。
保持了 HashSet 的性能优势,同时提供顺序维护功能。
特点
有序:元素按插入顺序存储。
不允许重复:每个元素在集合中唯一。
允许 null:可以包含一个 null 元素。
性能:插入、删除、查找的时间复杂度为 O(1)(平均情况下),但略低于 HashSet 因为维护了顺序。
Set<Integer> s = new LinkedHashSet<>();
s.add(11);
s.add(34);
s.add(25);
s.add(11);
s.add(61);
s.add(9);
System.out.println(s);//[11, 34, 25, 61, 9]
//输出顺序和添加顺序相同
TreeSet
TreeSet 是 Java 中的一个集合类,基于 NavigableSet 接口和 AbstractSet 类实现,底层依赖于 TreeMap,使用红黑树来存储元素。
特点
有序性:
元素按自然顺序排序(降序),或根据提供的 Comparator 进行排序。
适合需要排序的场景。
不允许重复:
每个元素在集合中唯一。
不允许 null 元素:
因为需要进行排序比较。
Set<Integer> s = new TreeSet<>(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2.compareTo(o1);
}
});
s.add(11);
s.add(34);
s.add(25);
s.add(11);
s.add(61);
s.add(9);
System.out.println(s);//[61, 34, 25, 11, 9]