`
nihao620
  • 浏览: 62238 次
  • 性别: Icon_minigender_2
社区版块
存档分类
最新评论

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来构造类的实体。

分享到:
评论

相关推荐

    谭浩强c++详细讲解

    书中还会讲解如何使用STL(Standard Template Library,标准模板库),这是C++库中的一大亮点,包括容器(如vector、list、set)、算法(如排序、查找)和迭代器等。STL极大地提高了C++程序员的生产力,让代码更加...

    c++经典课件详细讲解c++精要

    "C++经典课件详细讲解c++精要"这套资源旨在为初学者提供深入理解和掌握C++的基础。课件内容全面,覆盖了C++语言的核心概念,帮助学习者逐步构建编程思维。 在C++的学习中,首先会接触到基础语法,包括变量、数据...

    经典讲解C# get set.pdf

    本文将为您详细讲解 C# get set 函数的使用和应用。 一、为什么需要 get set 函数? 在面向对象编程中,类的成员变量通常是私有的,以保护类的内部状态不被外部修改。但是,为了使类的成员变量能够被外部访问和...

    Java集合Collection、List、Set、Map使用详细讲解.doc

    集合框架包括了多种接口和类,如Collection、List、Set、Map等,它们各自有特定的功能和用途。 1. **集合框架概述** - **容器简介**:集合框架就是一组接口和类,它们定义了存储、检索和操作对象的标准方式。这些...

    Java集合排序及java集合类详解(Collection、List、Map、Set)讲解.pdf

    Java集合排序及java集合类详解(Collection、List、Map、Set)讲解 Java集合框架是Java语言中最重要的组件之一,能够正确使用Java集合框架对于Java程序的开发具有无比的好处。本文将详细解释Java集合框架的实现原理、...

    set容器对类进行排序

    本文将详细讲解如何利用`set`容器对类进行排序,并探讨相关的关键知识点。 首先,`set`容器内部基于红黑树(Red-Black Tree)数据结构,保证了插入、删除和查找操作的时间复杂度为O(log n)。这意味着即使在大量元素...

    经典讲解C# get set

    ### C#中的Get和Set详解:提升代码安全性与封装性 在C#编程语言中,`get` 和 `set` 是实现属性访问器的核心组成部分,它们不仅增强了代码的安全性,还提高了程序的封装性,是面向对象编程(OOP)的重要实践之一。本文...

    STL容器 内容全,讲解详细 包括Vector、Deque、sort、set、map等

    本资源包含对STL中多种关键容器的详细讲解,包括Vector、Deque、sort、set、map等,这些都是C++程序员在实际开发中不可或缺的工具。 1. **Vector**:Vector是一个动态数组,它允许在任何位置插入和删除元素。其底层...

    jstl标签详细讲解

    group.setName("尚学堂"); User user = new User(); user.setUsername("张三"); user.setAge(18); user.setGroup(group); request.setAttribute("user", user); return mapping.findForward("success"); } ...

    get set方法生成注释和字段注释.zip

    get set方法生成注释和字段注释.zip,包括GetterSetterUtil.java、GetterSetterUtil.class、get set方法生成注释和字段注释.docx详细讲解如果用快捷方式生成set、get注释

    C语言文件操作函数大全(详细讲解)

    下面是C语言文件操作函数大全的详细讲解: 1. 文件打开函数:fopen函数 fopen函数是C语言中用于打开文件的函数,格式为:fopen(文件名, 文件使用方式)。文件使用方式可以是“r”、“rb”、“w”、“wb”、“a”、...

    经典讲解C# get set.docx

    C#中的get和set是访问器,用于控制类的私有成员(如字段)的访问。它们是构建属性的关键部分,属性是C#中一种特殊的方法,提供了对类内部数据的封装和保护。属性并不直接表示内存位置,而是提供了一种访问和修改对象...

    c++编程视频详细讲解

    本“C++编程视频详细讲解”是一套精心设计的教学资源,旨在帮助初学者和有一定基础的学习者深入理解和掌握C++的核心概念与实践技能。 视频教程通常采用直观易懂的方式,由专家亲自讲解,通过实例演示和针对性的指导...

    PYTHON学习教程:使用dict和set代码知识点讲解.docx

    Python 中的 dict 和 set 代码知识点讲解 dict 是 Python 中的一种内置数据结构,orthand for dictionary,用于存储键值对(key-value)。dict 的特点是可以快速查找和插入元素,不会随着元素的增加而变慢。下面是 ...

    VB的GET SET方法批量生成加使用说明书

    本篇文章将详细讲解GET和SET方法的原理,以及如何在VB中批量生成和使用它们。 GET方法在VB中用于获取对象的属性值。当你在代码中读取一个对象的属性时,实际上调用了该属性的GET方法。例如,如果你有一个名为`...

    【JavaScript源代码】vue 中this.$set 动态绑定数据的案例讲解.docx

    然而,在某些场景下,我们可能需要在运行时动态地添加或修改数据属性,这时就需要用到 `this.$set` 方法。`this.$set` 是 Vue 提供的一个全局方法,用于向响应式对象中添加新属性并确保该属性能触发视图更新。 案例...

    OpenFlashChart实例 + 详细讲解

    在"OpenFlashChart实例 + 详细讲解"中,我们将深入探讨如何使用这个库来创建引人入胜的数据可视化效果。 首先,OpenFlashChart的基本使用涉及到在HTML中引入库文件,这通常是一个SWF文件和JavaScript文件。SWF文件...

    REUSE_ALV_GRID_DISPLAY超详细讲解

    REUSE_ALV_GRID_DISPLAY超详细讲解 REUSE_ALV_GRID_DISPLAY是一个功能强大的ABAP函数模块,主要用于在ABAP程序中显示ALV网格控件。该函数模块提供了大量的参数和事件,使得开发者可以根据需要自定义ALV网格控件的...

    snmp协议详细讲解

    SNMP协议数据单元(PDUs)包括GetRequest、GetNextRequest、SetRequest、Trap和GetResponse。这些PDUs构成了SNMP的基本操作,如查询、修改配置和接收陷阱(Trap,即设备主动发送的报警信息)。 5. 管理信息结构SMI ...

Global site tag (gtag.js) - Google Analytics