前 言 🍉 作者简介:半旧518,长跑型选手,立志坚持写10年博客,专注于java后端 ☕专栏简介:深入、全面、系统的介绍java的基础知识 🌰 文章简介:本文将介绍java常用的set集合,建议收藏备用,创作不易,敬请三连哦 。
文章目录
- 1 HashSet类
- 2 TreeSet类
- 3 EnumSet类
- 4 各Set类的性能分析
Set集合与Collection基本相同,具有一些不同的行为。Set集合中的元素是无序的,不可以重复添加元素。
1 HashSet类
集合值可以为null,不是同步的,元素是无序的,通过哈希算法计算元素存储的位置,因而可以自动增加其容量。HashSet中元素使用的槽位是“桶‘,如果两个对象的equals()方法返回值是true,会放在一个桶里,通过链式方法存储。因此最好避免出现不同对象equals方法返回true的情况,防止性能下降。
如果把一个对象放入HashSet集合中,重写equals()方法时,也应该重写hashcode()方法。规则是:如果两个对象的equals()返回值是true,则hashcode值应该相同。重写hashcode()方法的步骤是:1.根据规则计算每个实例变量的hashcode值 2.各个实例变量乘以一个质数加权相加。
如果是可变对象,后面的Java程序改变该对象,使该对象中实例变量与另一个对象相同,就可能导致两个对象的hashcode值相同。这会导致HashSettings发生混乱,这个被修改了的对象无法访问了(无法计算hashcode找到它的真实位置了)。当我们把一个元素装入HashSet集合中后,尽量不要修改参与计算hashcode值的实例变量。
LinkedHashSet类是HashSet类的子类,在物理上是根据hash算法存储元素,但是还通过链表方式来维护其元素顺序。
2 TreeSet类
TreeSet类是SorttedSet接口的实现类。其元素是排序(按大小)的。
public class TreeSetTest {
public static void main(String[] args) {
TreeSet nums = new TreeSet();
nums.add(5);
nums.add(2);
nums.add(10);
nums.add(-9);
// [-9, 2, 5, 10]
System.out.println(nums);
System.out.println(nums.first());
// 返回小于4的子集 [-9, 2]
System.out.println(nums.headSet(4));
// [5, 10]
System.out.println(nums.tailSet(5));
//[2]
System.out.println(nums.subSet(-3, 4));
}
}
TreeSet是通过调用集合元素的commpareTo(Object obj)方法比较元素的大小关系。如果试图把一个对象添加到TreeSet时,则该对象必须实现Comparable接口,否则会抛出异常。
class Err {
}
public class TreeSetErrorTest {
public static void main(String[] args) {
TreeSet ts = new TreeSet();
ts.add(new Err());
//添加第二个元素时自动调用compareTo()反复,引发ClassCastException
ts.add(new Err());
}
}
```
TreeSet中元素的比较完全是基于compareTo()方法来进行的。
```java
class Z implements Comparable {
int age;
public Z(int age) {
this.age = age;
}
public int compareTo(Object o) {
return 1;
}
public boolean equals(Object o) {
return true;
}
@Override
public String toString() {
return "Z [age=" + age + "]";
}
}
public class TreeSetTest2 {
public static void main(String[] args) {
TreeSet ts = new TreeSet();
Z z1 = new Z(6);
ts.add(z1);
// 打印true,即使是同一个对象,但由于compareTo()方法的返回值是1,仍然可以添加
System.out.println(ts.add(z1));
// [Z [age=6], Z [age=6]]
System.out.println(ts);
((Z) ts.first()).age = 9;
//[Z [age=9], Z [age=9]]
System.out.println(ts);
}
}
TreeSet中的对象存储可参考下图
但是如果一个可变的对象放入TreeSet中后,该对象的实例变量如果在后续代码中改变了,TreeSet不会再调整它们的顺序。该元素也无法被删除了(甚至与该元素值相同的未被修改的元素也无法被删除)。
可以通过Coparator自定义TreeSet的比较规则。
class M {
int age;
public M(int age) {
this.age = age;
}
@Override
public String toString() {
return "M [age=" + age + "]";
}
}
public class TreeSetTest3 {
public static void main(String[] args) {
// 此处Lambda表达式的目标类型是comparator
TreeSet ts = new TreeSet((o1, o2) -> {
M m1 = (M) o1;
M m2 = (M) o2;
return m1.age > m2.age ? -1 : m1.age < m2.age ? 1 : 0;
});
ts.add(new M(5));
ts.add(new M(-3));
ts.add(new M(4));
//[M [age=5], M [age=4], M [age=-3]]
System.out.println(ts);
}
}
3 EnumSet类
EnumSet类专门为枚举类型数据设计,在内部以位向量形式存储数据,这种高效的存储方式使EnumSet对象占用内存小,效率高(尤其是在进行批处理时)。EnumSet中不允许加入null值,否则会报出空指针异常。
enum Season {
SPRING, SUMMER, FALL, WINTER
}
public class EnumSetTest {
public static void main(String[] args) {
EnumSet es1 = EnumSet.allOf(Season.class);
System.out.println(es1);
EnumSet es2 = EnumSet.noneOf(Season.class);
// []
System.out.println(es2);
es2.add(Season.WINTER);
System.out.println(es2);
}
}
4 各Set类的性能分析
HashSet在查找、增加数据时性能都要比TreeSet更好,这是因为TreeSet底层要通过红黑树来维护,只有需要保持排序的存储元素时才选用TreeSet。LinkedHashSet在增加、删除元素时,比HashSet略慢,这是链表造成的开销,但是由于链表结构,LinkedHashSet会在遍历上更快。EnumSet是所有Set中性能最好的,但是只能存储枚举类型数据。Set集合都是线程非安全的,可以使用collections工具类中sychronizedSortedSet方法包装该集合来实现同步。
SortedSet s=Collections.synchronizedSortedSet(new TreeSet());
这篇set集合的内容就介绍到这里结束了,学习集合建议适当刷题,这里给推荐一个大厂刷题网站:牛客网|大厂面试笔试真题汇总