Set集合
Set集合和Collection集合基本相同,行为略有不同,即无序,不允许重复。
HashSet类
大多数使用Set的时候,我们基本都使用HashSet类。HashSet类使用hash算法来角色元素在集合中的存储位置,因此具有很好的存取和查找速度。特点如下:
- 元素无序,即你添加的顺序可能和你遍历出来的顺序不同。
- 线程不安全,多个线程同时修改HashSet中的元素时,需要同步保证安全
- 集合元素可以是null
HashSet如何存储元素?
当向HashSet中添加元素时,HashSet先调用该元素的hashCode()方法,获取该对象的hashCode值,然后根据hashCode值决定该元素的存储位置;
当两个元素的hashCode值相同时,就会调用元素的equals()方法,比较该元素是否相等,如果相等,则后添加的元素添加失败,如果不相等,则会在该存储位置形成链式结构(链表)来存储,此时会导致性能下降
综上,HashSet是通过hashCode()和equals()判断元素是否相同的;为了性能考虑,当重新hashCode()和equals()方法时,尽量保证,当两个对象的hashCode值相同时,equals()比较返回true
具体实例如下:
public class CollectionTest {
class A{
@Override
public int hashCode(){
return 1;
}
}
class B{
@Override
public boolean equals(Object obj){
return true;
}
}
class C{
@Override
public int hashCode(){
return 2;
}
@Override
public boolean equals(Object obj){
return true;
}
}
public static void main(String[] args) {
CollectionTest c = new CollectionTest();
c.test();
}
public void test(){
HashSet<Object> hash = new HashSet<>();
hash.add(new A());
hash.add(new A());
hash.add(new B());
hash.add(new B());
hash.add(new C());
hash.add(new C());
System.out.println(hash);
}
}
输出结果如下:
[CollectionTest$A@1, CollectionTest$A@1, CollectionTest$C@2, CollectionTest$B@677327b6, CollectionTest$B@1540e19d]
可发现A类和B类对象可以添加两个,而C类对象只能添加一个;表明HashSet判断是否未同意对象的标准是hashCode()、equals()同时相等LinkedHashSet类
LinkedHashSet类是HashSet类的子类,也是通过对象hashCode值决定对象存储位置,通过hashCode值和equals()判断对象是否相同。
和HashSet类的区别是:LinkedHashSet类的内部元素通过链表连接在一起,导致内部元素时有序的;元素的顺序和添加顺序相同
TreeSet类
TreeSet类是SortedSet的实现类,顾名思义TreeSet可以保证集合中元素的排序状态。TreeSet提供了独特的方法:
- Object first():返回集合第一个元素
- Object last():返回集合的最后一个元素
- Object lower(Object obj):返回比obj小的最大元素
- Object higher(Object obj):返回比obj大的最小的元素
- SortedSet subSet(Object obj1,Object obj2):返回集合中在obj1和obj2之间的元素
- SortSet headSet(Object obj):返回集中中所有小于obj的元素集合
- SortSet tailSet(Object obj):返回集合中所有大于obj的元素集合
HashSet采用hashCode决定元素的存储位置,TreeSet采用红黑树来存储集合元素。
TreeSet类如何判断元素大小?
自然排序
TreeSet中元素实现Comparable集合,重写其中的int compareTo(Object obj),通过返回判断元素大小。
Java中常用类已实现该接口:
- BigDecimal/BigInteger和所有数字类型的包装类(Integer等)
- Character/Boolean/String/Date等
当向TreeSet中添加元素第二个元素时,会将该元素和第一个元素相比,决定元素的顺序。之后添加的元素和一次如此。
因此集合中元素必须实现Comparable接口,否则无法比较大小,导致出现抛出异常。TreeSet元素是否相等的标准是
int compareTo(Object obj)是否返回0
public class Main {
class A implements Comparable{
@Override
public int compareTo(Object o) {
return 1;
}
}
public static void main(String[] args) {
Main main = new Main();
main.test();
}
public void test(){
TreeSet<A> tree = new TreeSet<>();
A a = new A();
tree.add(a);
tree.add(a);
System.out.println(tree);
}
}
输出结果如下:
[Main$A@1540e19d, Main$A@1540e19d]
可以发现将同一个对象同时添加到TreeSet中成功了。因此判断equals()和compareTo(Object obj)的判断标准应该相同,否则与Set集合的规则向冲突。
当修改TreeSet中的元素导致集合中两个元素相等时,会导致接下来的删除这两个相等的元素失败。但是,一旦删除集合中其他元素成功后,将导致重新索引,此时有可以删除成功了。
定制排序
TreeSet<B> treeSet = new TreeSet<>(new Comparator<B>() {
@Override
public int compare(B o1, B o2) {
return 0;
}
});
只需创建TreeSet对象时,重写compare(object obj1,Object obj2)方法即可
性能比较
HashSet和TreeSet是两个具有代表性的Set集合,HashSet的性能比TreeSet优秀,因为TreeSet需要维护一个红黑树来保证集合元素的顺序。因此如果没有排序需求,则选择使用HashSet。
HashSet和LinkedHashSet相比,HashSet的性能要好,因为LinkedHashSet要维护链表。但也是因为链表,导致LinkedHashSet遍历集合元素的性能要优秀