Set详细讲解

 

结构图:


                 |--------SortedSet--------TreeSet

                 |

                 |------------HashSet

  Set--------- |

                 |------------LinkedHashSet

                 |

                 |------------CopyOnwriteArraySet



Set(interface): 存入Set的每个元素必须是唯一的,因为Set不保存重复元素。加入Set的Object必须定义equals()方法以确保对象的唯一性。Set与Collection有完全一样的接口。Set接口不保证维护元素的次序。

  HashSet: 为快速查找而设计的Set。存入HashSet的对象必须定义hashCode()。

  TreeSet: 保持次序的Set,底层为树结构。使用它可以从Set中提取有序的序列。

  LinkedHashSet: 具有HashSet的查询速度,且内部使用链表维护元素的顺序(插入的次序)。于是在使用迭代器遍历Set时,结果会按元素插入的次序显示。

  HashSet采用
散列函数 对元素进行排序,这是专门为快速查询而设计的;TreeSet采用 红黑树 的数据结构进行排序元 素;LinkedHashSet内部使用散列以加快查询速度,同时使用链表维护元素的次序,使得看起来元素是以插入的顺序保存的。需要注意的是,生成自己 的类时,Set需要维护元素的存储顺序,因此要实现Comparable接口并定义compareTo()方法。


1、TreeSet部分:
   以下以TreeSet为例进行分析。
   请看TreeSet的部分实体:

 

public class TreeSet<E> extends AbstractSet<E>
      implements SortedSet<E>, Cloneable, java.io.Serializable
 {
  // The backing Map
      private transient SortedMap<E,Object> m; 
      // The keySet view of the backing Map
      private transient Set<E> keySet; 
      // Dummy value to associate with an Object in the backing Map
      //这是每个键所指的对像
      private static final Object PRESENT = new Object();
      //constructor
      private TreeSet(SortedMap<E,Object> m) {
          this.m = m;
           keySet = m.keySet();
      }
      public TreeSet() {
   this(new TreeMap<E,Object>());
      }
      //以下省略..........
 }
 

 


    可以看到TreeSet使用了SortedMap作为其Map保存“键-值”对,而这个SortedMap的真正实体是TreeMap。
    
    请看示例程序1:

 

import java.util.*;
 public class SetTest1 {
  public static void main(String[] args){
   Set set = new TreeSet();
   set.add(new SetElement1("aa"));
   set.add(new SetElement1("bb"));
  }
  static class SetElement1{
   String s;
   public SetElement1(String s){
    this.s =  s;
   }
   public String toString(){
    return s;
   }
   public boolean equals(Object obj) {
    return s.equals(((SetElement1)obj).s);
   }
  }
 }
 


    该程序能够正常编译,但是运行时会抛出异常java.lang.ClassCastException。为什么?
    
    请看示例程序2:
 

import java.util.*;
 public class SetTest2 {
  public static void main(String[] args){
   Set set = new TreeSet();
   set.add(new SetElement2("aa"));
   set.add(new SetElement2("aa"));
   set.add(new SetElement2("bb"));
   System.out.println(set);
  }
  static class SetElement2 implements Comparable{
   String s;
   public SetElement2(String s){
    this.s =  s;
   }
   public String toString(){
    return s;
   }
   public int compareTo(Object o){
    return s.compareTo(((SetElement2)o).s);
   }
   public boolean equals(Object obj) {
    return s.equals(((SetElement2)obj).s);
   }
  }
 }
 

 

   运行结果:
   [aa, bb]
   这正是我们所期望的结果。那“示例程序1”和“示例程序2”有什么区别?
   是因为SetElement2实现了Comparable接口,而SetElement1没有。SetElement2实现Comparable接 口有什么用呢?因为在TreeSet的add方法中需要比较两个    元素的“值”。请看TreeMap中的compare方法:
  

private int compare(K k1, K k2) {
        return (comparator==null ? ((Comparable</*-*/K>)k1).compareTo(k2)
                                 : comparator.compare((K)k1, (K)k2));
   }
 

 

 

   可见这个方法先把要比较的元素down cast成Comparable类型。这里就可以解释“示例程序1”中为什么会抛出异常 java.lang.ClassCastException,因SetElement1没有实现Comparable接口,当然就不能down cast 成Comparable。可见,要用TreeSet来做为你的Set,那么Set中所装的元素都必须实现了Comparable接口。
   说到这里,你是不是想到了TreeSet中是采用Comparable接口中的compareTo方法来判断元素是否相同(duplicate),而不是采用其他类似equals之类的东东来判断。
   
   请看示例程序3:
   

import java.util.Set;
 import java.util.*;
 public class SetTest3 {
  public static void main(String[] args){
   Set set = new HashSet();
   set.add(new SetElement3("aa"));
   set.add(new SetElement3("aa"));
   set.add(new SetElement3("bb"));
   System.out.println(set);
  }
  static class SetElement3 implements Comparable{
   String s;
   public SetElement3(String s){
    this.s =  s;
   }
   public String toString(){
    return s;
   }
   public int compareTo(Object o){
    //return s.compareTo(((SetElement3)o).s);
    return -1;
   }
   public boolean equals(Object obj) {
    return s.equals(((SetElement3)obj).s);
   }
  }
 }
 

 

   运行结果:
   [bb, aa, aa]
   看到没有,有两个“aa”!!这是因为compareTo返回值始终是"-1",也就是说“把任何元素都看成不同”。


2、HashSet部分:
   以下以HashSet为例进行分析。
   从Hashset类的主体部分:
 

public class HashSet<E> extends AbstractSet<E>
     implements Set<E>, Cloneable, java.io.Serializable
 {
  static final long serialVersionUID = -5024744406713321676L;
  private transient HashMap<E,Object> map;
  // Dummy value to associate with an Object in the backing Map
  //这是每个键所指的对像
  private static final Object PRESENT = new Object();

     public HashSet() {
   map = new HashMap<E,Object>();
      }
     public boolean add(E o) {
   return map.put(o, PRESENT)==null;
      }
    //以下省略..........
    }
 
        public HashSet() {
 
  map = new HashMap<E,Object>();
    
 }
 

 

   可以看到HashSet使用了HashMap作为其Map保存“键-值”对。
   
   请看示例程序4:
 

import java.util.*;

 public class SetTest4 {
 public static void main(String[] args){
  Set set = new HashSet();
  set.add(new SetElement4("aa"));
  set.add(new SetElement4("aa"));
  set.add(new SetElement4("bb"));
  System.out.println(set);
 }
 static class SetElement4{
  String s;
  public SetElement4(String s){
   this.s =  s;
  }
  public String toString(){
   return s;
  }
  public boolean equals(Object obj) {
   return s.equals(((SetElement4)obj).s);
  }
 }
}
 

 

   运行结果:
   [bb, aa, aa]
   没有“示例程序1”中的java.lang.ClassCastException,但是运行结果似乎不对,因为有两个“aa”。
   
   请看示例程序5:
 

import java.util.*;
 public class SetTest5 {
  public static void main(String[] args){
   Set set = new HashSet();
   set.add(new SetElement5("aa"));
   set.add(new SetElement5("aa"));
   set.add(new SetElement5("bb"));
   System.out.println(set);
  }
  static class SetElement5{
   String s;
   public SetElement5(String s){
    this.s =  s;
   }
   public String toString(){
    return s;
   }
   public boolean equals(Object obj) {
    return s.equals(((SetElement5)obj).s);
   }
   public int hashCode() {
    //return super.hashCode();
    return s.hashCode();
   }
  }
 }
 

 

    运行结果:
    [bb, aa]
    这就对了。“示例程序4”和“示例程序5”有什么区别?是SetElement5重写了hashCode方法。
    
    可见HashSet中是采用了比较元素hashCode的方法来判断元素是否相同(duplicate),而不是采用其他类似equals之类的东东来判断。
 


3、CopyOnWriteArraySet部分:
   类CopyOnWriteArraySet是java.util.concurrent包中的一个类,所以它是线程安全的。
   CopyOnWriteArraySet是使用CopyOnWriteArrayList作为其盛放元素的容器。当往CopyOnWriteArrayList添加新元素,它都要遍历整个List,并且用equals来    比较两个元素是否相同。

   请看示例程序6:
 

import java.util.*;
 import java.util.concurrent.*;
 public class SetTest6 {
  public static void main(String[] args){
   Set set = new CopyOnWriteArraySet();
   set.add(new SetElement6("aa"));
   set.add(new SetElement6("aa"));
   set.add(new SetElement6("bb"));
   System.out.println(set);
  }
  static class SetElement6{
   String s;
   public SetElement6(String s){
    this.s =  s;
   }
   public String toString(){
    return s;
   }
   public boolean equals(Object obj) {
    return s.equals(((SetElement6)obj).s);
   }
  }
 }
 

 

   运行结果:
   [aa, bb]
   好了,一切搞定!!


4、总结:
 
   Set中实现元素互异的各种方法差异很大,大致可以分为三种:使用equals,使用hashCode,使用compareTo。但是我还没有发现 采用“判断地址空间是否相同”来判断元素是否相同的类,当然我们可以用现有的三种方法来实现“判断地址空间是否相同”。
   
   综上所述,我们可以总结出使用Set的三种不同的情形:(以下假设元素类为Element)
   A、如果想使用Element的equals方法来判断元素是否相同,那么可以使用CopyOnWriteArraySet来构造类的实体。
   B、如果Element实现了Comparable接口,而且想使用compareTo方法来判断元素是否相同,那么可以使用TreeSet来构造类的实体。
   C、如果想使用判断hashCode是否相同的方法来判断元素是否相同,那么可以使用HashSet来构造类的实体。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值