本人在很多情况下会使用Set集合去做针对数据唯一性的处理,但是Set有那么多种,什么场景使用哪种Set是一个值得思考的问题。希望可以帮助到各位。
一,概念详解:
Set 是一个不允许包含重复元素的集合。HashSet、TreeSet 和 LinkedHashSet 都是 Set 接口的实现类,它们各自具有不同的特性和使用场景。
- HashSet:基于哈希表实现,其元素是无序的。添加、删除和查找操作的时间复杂度都是 O(1)。它不保证元素的顺序,也不允许包含 null 元素。当向 HashSet 集合中添加元素时,它会调用该对象的 hashCode() 方法来决定元素在集合中的存储位置。如果两个对象通过 equals() 方法比较相等,并且它们的 hashCode() 方法返回值也相等,则它们被认为是相等的。
- TreeSet:是 SortedSet 的实现类,允许根据元素的自然顺序或者创建时提供的 Comparator 进行排序。它使用树结构(通常是红黑树)来存储元素,因此添加、删除和查找操作的时间复杂度为 O(log n)。TreeSet 允许包含 null 元素,但只能有一个。与 HashSet 相比,TreeSet 在迭代访问集合中的全部元素时性能较好,但在插入元素时的性能较差。
- LinkedHashSet:底层数据结构由哈希表和链表组成。它根据元素的 hashCode 值来决定元素的存储位置,同时使用链表维护元素的顺序。因此,LinkedHashSet 中的元素顺序与它们被添加到集合中的顺序一致。它不允许有重复的元素,但可以有一个 null 元素。与 HashSet 相比,LinkedHashSet 在插入和访问元素时的性能略逊一筹,但在迭代访问集合中的全部元素时性能较好。个人认为LinkedHashSet相当于List的去重版本(因为List的本身就是有序的)
我这里提供三种模拟现实场景(仅供参考):
场景1:需要快速插入、删除和查找操作且不关心元素的顺序;
场景2:需要一个自动排序的集合(或者有自定义排序需求)如自然数;
场景3:需要保持元素的插入顺序或者对链表操作有特殊需求;
是否能包含null | 数据结构 | 顺序 | 适用场景 | |
HashSet | 否 | 哈希表 | 无序 | 场景1 |
LinkedHashSet | 是(只能一个) | 红黑树 | 与被添加到集合时的顺序一致 | 场景3 |
TreeSet | 是(只能一个) | 哈希表和链表 | 允许自然排序或者自定义排序 | 场景2 |
#自然排序:是指根据元素的大小顺序进行排序,要求元素所属的类实现Comparable接口,并重写compareTo方法来定义排序规则。
二、代码详解:
HashSet:
(1)对于集合中值为数字的情况
public static void main(String[] args) {
HashSet<Integer> set = new HashSet<>();
set.add(5);
set.add(2);
set.add(8);
set.add(1);
set.add(2);
set.add(4);
for (Integer num : set) {
System.out.println(num);
}
}
结果:
是不是有点疑惑。哈哈哈,那是因为HashSet中数据为数字类型的,由于数字类型的hashCode方法实现可能存在规律性,可能导致元素在桶中的顺序出现一定的规律。所以导致结果是顺序的
(2)对于集合中值为字符串的情况
public static void main(String[] args) {
HashSet<String> set = new HashSet<>();
set.add("我是5");
set.add("我是2");
set.add("我是8");
set.add("我是1");
set.add("我是2");
set.add("我是4");
for (String num : set) {
System.out.println(num);
}
}
结果:
2.LinkedHashSet(顺序与插入顺序一致)
(1)对于集合中值为数字的情况
public static void main(String[] args) {
LinkedHashSet<Integer> set = new LinkedHashSet<>();
set.add(5);
set.add(2);
set.add(8);
set.add(1);
set.add(2);
set.add(4);
for (Integer num : set) {
System.out.println(num);
}
}
结果:
(2)对于集合中值为字符串的情况
public static void main(String[] args) {
LinkedHashSet<String> set = new LinkedHashSet<>();
set.add("我是5");
set.add("我是2");
set.add("我是8");
set.add("我是1");
set.add("我是2");
set.add("我是4");
for (String num : set) {
System.out.println(num);
}
}
结果:
3.TresSet(自然排序):
(1)对于集合中值为数字的情况
public static void main(String[] args) {
TreeSet<Integer> set = new TreeSet<>();
set.add(5);
set.add(2);
set.add(8);
set.add(1);
set.add(2);
set.add(4);
for (Integer num : set) {
System.out.println(num);
}
}
结果:
(2)对于集合中值为字符串的情况
public static void main(String[] args) {
TreeSet<String> set = new TreeSet<>();
set.add("我是5");
set.add("我是2");
set.add("我是8");
set.add("我是1");
set.add("我是2");
set.add("我是4");
for (String num : set) {
System.out.println(num);
}
}
结果如下:
总结:HashSet、TreeSet 和 LinkedHashSet 各有其特点,选择使用哪一种取决于具体的需求和使用场景。如果需要快速插入、删除和查找操作且不关心元素的顺序,可以选择 HashSet;如果需要一个自动排序的集合,可以选择 TreeSet;如果需要保持元素的插入顺序或者对链表操作有特殊需求,可以选择 LinkedHashSet。我们可以先不纠结这些集合的底层逻辑,首先得明白什么场景下使用合适的才是第一步,慢慢掌握底层逻辑。
希望我的文章能帮到您,感谢您的阅读,愿我们共同进步,加油!!