• Java-集合第二篇Set集合


    1、Set集合与Collection基本相同,没有提供额外的方法。实际上Set就是Collection,只是行为略有所不同(Set不允许有重复元素)。

         Set下的HashSet、TreeSet、EnumSet完全适用于上面Set的有关规则(即元素不能够重复)。

         

    2、HashSet

     (1)HashSet按Hash算法来存储集合中的元素,因此具有良好的存取和查找性能。

            HashSet具有的特点:

              1》不能保证元素的排列顺序。

              2》HashSet不是同步的,即不是线程安全的。

              3》集合元素值可以为null。

      (2)当向HashSet集合中存入一个元素时,HashSet会调用该对象的hashCode()方法来得到该对象的hashCode值,然后根据该hashCode值决定该对象在HashSet中的存储位置。

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

          1》如果有两个元素通过equals()方法比较返回true,但它们的hashCode()方法返回值不相等,HashSet就认为它们是不同的元素,依然可以添加到HashSet中,它们将存储在不同的位置。(注:public boolean equals(Object o)、public int hashCode()方法均来自Object

          所以,当某种类型需要放到HashSet中,并且需要重写其equals()方法,则相应的hashCode()方法也应该重写。规则是:如果两个对象通过equals()方法比较返回返回true,这两个对象的hashCode值也应该相同。

          2》如果两个元素比较equals()方法返回true,hashCode()返回不同的值,这与Set集合的规则冲突;equals()返回false,hashCode()返回相同的值,HashSet将试图把它们保存在同一个位置,但又不行,这时候在这个位置使用链式结构保存多个对象

          HashSet访问元素是根据元素的hashCode值来快速定位的,如果HashSet中存在两个以上的元素具有相同的hashCode值(上面使用链式结构保存),将会导致性能下降。

          3》HashSet中每个能存储元素的“槽位”(slot)通常也称为“桶”(bucket),如果多个元素equals()返回false,hashCode()返回相同hashCode(链式结构存储),就需要在“桶”中放多个元素,从而导致性能下降。重写hashCode()方法的基本规则:

            1>同一个对象调用多次hashCode()方法应该返回相同的值。

            2>当equals()方法返回true时,hashCode()应该返回相同的hashCode值。

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

       (3)重写hashCode()方法的一般步骤

            1》把对象内每个有意义的实例变量(即每个参与equals()方法比较标准的实例变量)计算出一个int类型的hashCode值。计算方式举例:

    实例变量类型 计算方式
    boolean hashCode=(f?0:1);
    float hashCode=Float.floatToIntBits(f);
    double

    long l=Double.doubleToLongBits(f);

    hashCode=(int)(l^(1>>>32));

    long hashCode=(int)(f^(f>>>32));
    整数类型(byte、short、char、int) hashCode=(int)f;
    引用类型 hashCode=f.hashCode();

            2》利用第一步计算得到的多个hashCode值组合计算出一个hashCode值返回,如直接相加作为最终的hashCode值返回。

            3》为了避免直接相加产生的偶然相等,可以通过为各实例变量的hashCode值乘以任意一个质数后再相加。

    return f1.hashCode()*17+(int)f2*29;

       (4)如果向HashSet中添加了一个可变对象后,后面程序修改了该可变对象的实例变量值,则可能导致它与集合中的其他元素相同(equals()返回true,hashCode()也返回相同的值),从而可能导致HashSet中包含两个相同的对象。这是一个值得注意的问题。

    3、LinkedHashSet

       也是通过元素的hashCode值决定元素的存储位置,但它同时使用链表维护元素的次序,遍历LinkedHashSet中的元素时,LinkedHashSet将会按照元素添加顺序来访问集合中的元素。

       LinkedHashSet需要维护元素的插入顺序,因此性能略低于HashSet的性能,但在迭代访问集合中的全部元素时将有很好的性能。

    4、TreeSet

       (1)TreeSet是SortedSet接口的实现实现类,TreeSet可以确保集合元素处于排序状态。与HashSet集合相比,TreeSet还提供了一下的额外方法:

          1》Comparator comparator():如果TreeSet采用了定制排序,则该方法返回定制排序所使用的Comparator;如果TreeSet采用了自然排序,则返回null。

          2》Object first():返回集合中的第一个元素。

          3》Object last():返回集合中的最后一个元素。

          4》Object lower(Object o):返回集合中位于指定元素之前的元素(即小于指定元素的最大元素,参考元素不需要是TreeSet集合里的元素)。

          5》Object higher(Object o):返回集合中位于指定元素之后的元素(即大于指定元素的最小元素,参考元素不需要是TreeSet集合里的元素)。

          6》SortedSet subSet(Object fromElement,Object toElement):返回此Set的子集合,范围从fromElement(包含)到toElement(不包含)。

          7》SortedSet headSet(Object toElement):返回此Set的子集,由小于toElement的元素组成。

          8》SortedSet tailSet(Object fromElement):返回此Set的子集,由大于或者等于fromElement的元素组成。

          Tips:TreeSet是有序的,所以上面的方法为其提供访问第一个、前一个、后一个、最后一个元素,还有3个从TreeSet中截取子元素的方法。

          TreeSet采用红黑树来决定元素的存储位置。TreeSet支持两种排序方式:自然排序和定制排序,默认采用自然排序。

      (2)自然排序

           TreeSet会调用集合元素的comparaTo(Object obj)方法来比较元素之间的大小关系,然后将集合元素按升序排列,这种方式就是自然排序。

           Java提供了一个Comparable接口,该接口里定义了一个compareTo(Object obj)方法,该方法返回一个整数值,实现该接口的类必须实现该方法,实现了该接口的类的对象就可以比较大小。当一个对象调用该方法与另一个对象进行比较时,如obj1.compareTo(obj2),返回0,则两个对象相等;返回正整数,则obj1大于obj2;返回负数,则obj1小于obj2。

          Java实现了Comparable接口的常用类:

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

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

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

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

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

         如果试图将一个对象添加到TreeSet时,则该对象的类必须实现Comparable接口,否则程序将会抛出异常(常见的强制类型转换错误)。注意,向TreeSet集合添加元素时,只有第一个元素无需实现Comparable接口,后面添加的所有元素都必须实现Comparable接口,这种时候能够成功添加,但当取元素的时候,还是会引发强制类型转换错误。

         所以,一般TreeSet中只能添加同一种类型的对象。

         当把一个对象加入TreeSet集合中时,TreeSet调用该对象的compareTo(Object obj)方法与容器中的其他对象比较大小,然后根据红黑树结构找到它的存储位置。如果两个对象通过compareTo(Object obj)方法比较相等,新对象将无法添加到TreeSet集合中。

         TreeSet判断两个元素是否相等的唯一标准是compareTo(Object obj)是否返回0,不返回0就是不相等。

    class Z implements Comparable{
      
      int age;
      public Z(int age){
         this.age=age;
      }
      @Override
      public boolean equals(Object obj){
        return true;
      }
      @Override
      public int compareTo(Object obj){
        return 1;
      }
    }
    
    public class TreeSetTest{
      TreeSet set=new TreeSet();
      Z z1=new Z(6);
      set.add(z1);
      set.add(z1);
    //第二次添加同一个对象,输出true,表名添加成功 System.out.println(set.add(z1));
    //输出set集合,看到两个元素 System.out.println(set);
    //修改第一个元素的age ((Z)(set.first())).age
    =9;
    //输出set,可以看到两个元素一样了!!! System.out.println((set); }

       从上面结果,由于equals()返回true,但compareTo()返回一个正整数,导致第一个元素和第二个元素是同一个元素,只是它们在集合中存储的是不同的引用值。

       当需要把一个对象放入TreeSet中,重写该对象对应类的equals()方法时,应该保证该方法与compareTo(Object obj)方法有一致的结果。其规则是:如果两个对象通过equals()方法比较返回true时,这两个对象通过compareTo(Object obj)方法比较应该返回0。

       如果compareTo()返回0,equals()返回false,TreeSet不会让第二个元素添加进集合,这与Set集合的规则冲突(非重复元素添加失败)。

       往TreeSet中添加可变对象,可能引发两个对象compareTo()返回0。

      (3)定制排序

           TreeSet的自然排序是根据集合元素的大小,TreeSet将它们以升序排序。如果需要定制排序,如降序排序,则可以通过Comparator接口的帮助。该接口包含一个int compare(T o1,T o2)方法,该方法用于比较o1和o2的大小:返回正整数,则o1大于o2;返回0,则o1等于o2;返回负数,则o1小于o2。

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

    5、EnumSet

        专门为枚举类设计的集合类。EnumSet中的所有元素都必须是指定枚举类型的枚举值,该枚举类型在创建EnumSet时显式或隐式地指定。EnumSet的集合元素也是有序的,EnumSet以枚举值在Enum类内的定义顺序来决定集合元素的顺序。

        EnumSet在内部以位向量的形式存储,这种存储形式非常紧凑、高效,因此EnumSet对象占用内存很小,而且运行效率很好。

        EnumSet集合不允许加入null元素,否则将抛出空指针异常。

        EnumSet类没有暴露任何构造器来创建该类的实例,但它提供了类方法创建对象。提供创建EnumSet对象的方法有:

          1》EnumSet allOf(Class elementType):创建一个包含指定枚举类里所有枚举值的EnumSet集合。

          2》EnumSet complement(EnumSet s):创建一个其元素类型与指定EnumSet里元素类型相同的EnumSet集合,新EnumSet集合包含原EnumSet集合所不包含的、此枚举类剩下的枚举值。

          3》EnumSet copyOf(Collection c):使用一个普通集合来创建EnumSet集合。

          4》EnumSet copyOf(EnumSet e):创建一个与指定EnumSet具有相同元素类型、相同集合元素的EnumSet集合。

          5》EnumSet noneOf(Class elementType):创建一个元素类型为指定枚举类型的空EnumSet。

          6》EnumSet of(Efirst,E...rest):创建一个包含一个或者多个枚举值的EnumSet集合,传入的多个枚举值必须属于同一个枚举值。

          7》EnumSet range(E from,E to):创建一个包含从from枚举值到to枚举值范围内所有枚举值的EnumSet集合。

    6、各Set实现类的性能分析

       HashSet的性能总是比TreeSet好(添加、查询元素),因为TreeSet需要额外的红黑树算法维护集合元素的次序。只有当需要一个保持排序的Set时,才应该使用TreeSet,否则都应该使用HashSet。

       HashSet的子类LinkedHashSet,对于普通的插入、删除操作,LinkedHashSet比HashSet要略微慢一点,这是由维护链表所带来的的额外开销造成的,但后期遍历会变得很快。

       EnumSet是所有Set实现类这种性能最好的,但局限于只能保存某一枚举类的枚举值作为集合元素。

       Set的3个实现类HashSet、LinkedHashSet、EnumSet,都不是线程安全的,通常可以通过Collections工具类的synchronizedSortedSet方法来“包装”该Set集合,此操作最好在创建时进行,以防止对Set集合的意外非同步访问。

    SortedSet s=Collections.synchronizedSortedSet(new TreeSet(...));
  • 相关阅读:
    P1342 请柬
    P1186 玛丽卡
    Scala 中下划线的用法
    IDEA2017 maven Spark HelloWorld项目(本地断点调试)
    Spark内存管理详解
    Spark基础知识
    scala基本语法
    分布式锁的一点理解
    Redis并发问题
    redis集群原理
  • 原文地址:https://www.cnblogs.com/ZeroMZ/p/11380504.html
Copyright © 2020-2023  润新知