zoukankan      html  css  js  c++  java
  • 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(...));
  • 相关阅读:
    关闭编辑easyui datagrid table
    sql 保留两位小数+四舍五入
    easyui DataGrid 工具类之 util js
    easyui DataGrid 工具类之 后台生成列
    easyui DataGrid 工具类之 WorkbookUtil class
    easyui DataGrid 工具类之 TableUtil class
    easyui DataGrid 工具类之 Utils class
    easyui DataGrid 工具类之 列属性class
    oracle 卸载
    “云时代架构”经典文章阅读感想七
  • 原文地址:https://www.cnblogs.com/ZeroMZ/p/11380504.html
Copyright © 2011-2022 走看看