前 言 🍉 作者简介:半旧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; | |
} | |
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集合的内容就介绍到这里结束了,学习集合建议适当刷题,这里给推荐一个大厂刷题网站:牛客网|大厂面试笔试真题汇总