集合框架(二)

Set接口

HashSet

  • HashSet简介

    HashSet是Set接口的典型实现,大多数时候使用Set集合时就是使用这个实现类。HashSet按Hash算法来存储集合中的元素,因此具有很好的存取和查找功能。

    HashSet具有以下特点:

  • HashSet 是一个没有重复元素的集合

  • 不能保证元素的排列顺序,顺序可能和添加顺序不同,顺序也有可能发生变化。

  • HashSet不是同步的,如果多个线程同时访问一个HashSet,假设有两个或两个以上线程同时修改了HashSet集合时,则必须通过代码来保证其同步。

  • 集合元素值可以是null。

  • HashSet通过iterator()返回的迭代器是fail-fast的。

    当向HashSet集合中存入一个元素时, HashSet会调用该对象的hashCode()方法来得到该对象的hashCode值,然后根据该hashCode值决定该对象在HashSet中的存储位置。如果有两个元素通过equals()方法比较返回true,但它们的hashCode()方法返回值不相等,HashSet将会把它们存储在不同的位置,依然可以添加成功。

    也就是说,HashSet集合判断两个元素相等的标准是两个对象通过equals()方法比较相等,并且两个对象的hashCode()方法返回值也相等。

    注意:当把一个对象放入 HashSet中时,如果需要重写该对象对应类的equals()方法,则也应该重写其hashCode()方法。规则是,如果两个对象通过equals()方法比较返回true,这两个对象的hashCode值也应该相同。

    如果两个对象通过equals()方法比较返回true,但这两个对象的hashCode()方法返回不同的hashCode值时,这将导致 HashSet会把两个对象保存在Hash表的不同位置,从而使这两个对象都可以添加成功,这就与Set集合的规则冲突了。

    如果两个对象的hashCode()方法返回的hashCode值相同,但它们通过equals()方法比较返回false时将更麻烦:因为两个对象的hashCode值相同, HashSet将试图把它们保存在同一个位置,但又不行(否则将只剩下一个对象),所以实际上会在这个位置用链式结构来保存多个对象;而 HashSet访问集合元素时也是根据元素的hashCode值来快速定位的,如果 HashSet中两个以上的元素具有相同的hashCode值,将会导致性能下降。

     HashSet中每个能存储元素的“槽位”(slot)通常称为“桶”(bucket),如果有多个元素的hashCode值相同,但它们通过equals()方法比较返回false,就需要在一个“桶”里放多个元素,这样会导致性能下降严重。

    hashCode()方法对于 HashSet的重要性非常大(实际上,对象的hashCode值对于后面的HashMap同样重要),下面给出重写hashCode()方法的基本规则:

  • 在程序运行过程中,同一个对象多次调用hashCode()方法应该返回相同的值。

  • 当两个对象通过equals()方法比较返回true时,这两个对象的hashCode()方法应返回相等的值。

  • 对象中用作equals()方法比较标准的实例变量,都应该用于计算hashCode值。

注意:

    当向 HashSet中添加可变对象时,必须十分小心。如果修改 HashSet集合中的对象,有可能导致该对象与集合中的其他对象相等,从而导致 HashSet无法准确访问该对象。

 

TreeSet

  • TreeSet简介

TreeSet是SortedSet接口的实现类,正如SortedSet名字所暗示的,TreeSet可以确保集合元素处于排序状态。

如果希望TreeSet能正常运行,TreeSet只能添加同一种类型的对象。

 

TreeSet 是一个有序的集合,它的作用是提供有序的Set集合。它继承于AbstractSet抽象类,实现了NavigableSet<E>, Cloneable, java.io.Serializable接口。

TreeSet 继承于AbstractSet,所以它是一个Set集合,具有Set的属性和方法。

TreeSet 实现了NavigableSet接口,意味着它支持一系列的导航方法。比如查找与指定目标最匹配项。

TreeSet 实现了Cloneable接口,意味着它能被克隆。

TreeSet 实现了java.io.Serializable接口,意味着它支持序列化。

 

TreeSet是基于TreeMap实现的。TreeSet中的元素支持2种排序方式:自然排序 或者 根据创建TreeSet 时提供的 Comparator 进行排序。这取决于使用的构造方法。默认情况下,TreeSet采用自然排序。

TreeSet为基本操作(add、remove 和 contains)提供受保证的 log(n) 时间开销。

另外,TreeSet是非同步的。 它的iterator 方法返回的迭代器是fail-fast的。

1.自然排序

    TreeSet会调用集合元素的compareTo(Object obj)方法来比较元素之间的大小关系,然后将集合元素按升序排序,这种方式就是自然排序。在TreeSet中,对于String、Integer等类型就按照该类型自身提供的compareTo()方法进行排序,如果是自定义对象,则必须让自定义对象实现Comparable接口并重写compareTo()方法。

    Java提供了一个Comparable接口,该接口里定义了一个compareTo(Object obj)方法,该方法返回一个整数值,实现该接口的类必须实现该方法,实现了该接口的类的对象就可以比较大小。当一个对象调用该方法与另一个对象进行比较时,例如obj

.compareTo(Object obj2),如果该方法返回0,则表明这两个对象相等;如果该方法返回一个正整数,则表明obj1大于obj2;如果该方法返回一个负整数,则表明obj1小于obj2。

    Java的一些常用类已经实现了Comparable接口,并提供了比较大小的标准。下面是实现了Comparable接口的常用类:

  • BigDecimal、BigInteger以及所有的数值型对应的包装类:按它们对应的数值大小进行比较。

  • Character:按字符的UNICODE值进行比较。

  • Boolean:true对应的包装类实例大于false对应的包装类实例。

  • String:按字符串中字符的UNICODE值进行比较。

  • Date、Time:后面的时间、日期比前面的时间、日期大。

如果试图把一个对象添加到TreeSet时,则该对象的类必须实现Comparable接口,否则程序将会抛出异常。在自然排序时,集合元素必须实现Comparable接口,否则将会引发运行时异常:ClassCastException——因此,TreeSet要求自然排序的集合元素都必须实现该接口。

为了让程序更加健壮,推荐不要修改放入HashSet和TreeSet集合中元素的关键字实例变量。

 

2.定制排序

    如果需要实现定制排序,则可以通过Comparator接口的帮助。该接口里包含一个int compare(T o1,T o2)方法,该方法用于比较o1和o2的大小:如果该方法返回正整数,则表明o1大于o2;如果该方法返回0,则表明o1等于o2;如果该方法返回负整数,则表明o1小于o2。

    如果需要实现定制排序,则需要在创建TreeSet集合对象时,提供一个Comparator对象与该TreeSet集合关联,由该Comparator对象负责集合元素的排序逻辑。由于Comparator是一个函数式接口,因此可使用Lambda表达式来代替Comparator对象。

import java.util.TreeSet;
class M{
    int age;

public M(int age) {
    this.age = age;
}

@Override
public String toString() {
    return super.toString();
    }
}

public class ComparatorTest {
    public static void main(String[] args) {
        TreeSet<M> treeSet = new TreeSet(((o1, o2) -> {
        M m1 = (M) o1;
        M m2 = (M) o2;
        //根据M对象的age属性来决定大小,age越大,M对象反而越小
        return m1.age > m2.age ? -1 : m1.age < m2.age ? 1 : 0;
        }));
    }
}

注意:

    当通过Comparator对象(或Lambda表达式)来实现TreeSet的定制排序时,依然不可以向TreeSet中添加类型不同的对象,否则会引发ClassCastException异常。使用定制排序时,TreeSet对集合元素排序不管集合元素本身的大小,而是由Comparator对象(或Lambda表达式)负责集合元素的排序规则。TreeSet判断两个集合元素相等的标准是:通过Comparator(或Lambda表达式)比较两个元素返回了0,这样TreeSet不会把第二个元素添加到集合中。

总结:comparable和comparator的区别

 

Comparable和Comparator比较器详细区别见:

https://blog.csdn.net/Crazypokerk_/article/details/90602101

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值