Java之HashSet

——————————————————————————————————————————————

一、类的继承和实现关系:

public class HashSet<E>

extends AbstractSet<E>

implements Set<E>, Cloneable, Serializable

——————————————————————————————————————————————

二、构造方法摘要:

HashSet() 

          构造一个新的空集合,其底层 HashMap 实例的默认初始容量是 16,加载因子是 0.75。 

HashSet(Collection<? extends E> c) 

          构造一个包含指定 collection 中的元素的新 set。 

HashSet(int initialCapacity) 

          构造一个新的空集合,其底层 HashMap 实例具有指定的初始容量和默认的加载因子(0.75)。 

HashSet(int initialCapacity, float loadFactor) 

          构造一个新的空集合,其底层 HashMap 实例具有指定的初始容量和指定的加载因子。 

——————————————————————————————————————————————

三、方法摘要:
 boolean add(E o) 

          如果此集合中还不包含指定元素,则添加指定元素。 

 void clear() 

          从此集合中移除所有元素。 

 Object clone() 

          返回此 HashSet 实例的浅表复制:并没有克隆这些元素本身。 

 boolean contains(Object o) 

          Returns 如果此集合不包含指定元素,则返回 true。 

 boolean isEmpty() 

          如果此集合不包含任何元素,则返回 true。 

 Iterator<E> iterator() 

          返回对此集合中元素进行迭代的迭代器。 

 boolean remove(Object o) 

          如果指定元素存在于此集合中,则将其移除。 

 int size() 

          返回此集合中的元素的数量(集合的容量)。 

——————————————————————————————————————————————

四、Hashset的一些特性以及和其他相似类的比较

HashSet与HasnMap区别和方法:

1. HashSet是通过HashMap实现的,TreeSet是通过TreeMap实现的,只不过Set用的只是Map的key 
2.  Map的key和Set都有一个共同的特性就是集合的唯一性,TreeMap更是多了一个排序的功能.
3.  hashCode和equal()是HashMap用的, 因为无需排序所以只需要关注定位和唯一性即可.
a. hashCode是用来计算hash值的,hash值是用来确定hash表索引的.
b. hash表中的一个索引处存放的是一张链表, 所以还要通过equal方法循环比较链上的每一个对象 才可以真正定位到键值对应的Entry. 
c. put时,如果hash表中没定位到,就在链表前加一个Entry,如果定位到了,则更换Entry中的value,并返回旧value 
4. 由于TreeMap需要排序,所以需要一个Comparator为键值进行大小比较,当然也是用Comparator定位的。
a. Comparator可以在创建TreeMap时指定 
b. 如果创建时没有指定,那么就会使用key.compareTo()方法,这就要求key必须实现Comparable接口.
TreeMap是使用Tree数据结构实现的,所以使用compare接口就可以完成定位了。


HashSet,TreeSet和LinkedHashSet的区别:

Set接口:
Set不允许包含相同的元素,如果试图把两个相同元素加入同一个集合中,add方法返回false。
Set判断两个对象相同不是使用==运算符,而是根据equals方法。也就是说,只要两个对象用equals方法比较返回true,Set就不会接受这两个对象。


HashSet:
HashSet有以下特点

对于 HashSet 而言,它是基于 HashMap 实现的,底层采用 HashMap 来保存所有元素,绝大部分方法都是通过调用 HashMap 的方法来实现的,

因此 HashSet 和 HashMap 两个集合在实现本质上是相同的,可以看下面的源码。

和HashMap一样, 对于HashSet中保存的对象,请注意正确重写其equals和hashCode方法,以保证放入的对象的唯一性。

如果集合中对象的类没有重写这两个方法,那么就会使用从object继承的来的方法,即比较两个对象的引用是否相同,即比较地址。

 不能保证元素的排列顺序,顺序有可能发生变化
不是同步
 集合元素可以是null,但 只能放入一个null
当向HashSet结合中存入一个元素时,HashSet会调用该对象的hashCode()方法来得到该对象的hashCode值,然后根据 hashCode值来决定该对象在HashSet中存储位置。
简单的说,HashSet集合判断两个元素相等的标准是两个对象通过equals方法比较相等,并且两个对象的hashCode()方法返回值相 等
注意,如果要把一个对象放入HashSet中,重写该对象对应类的equals方法,也应该重写其hashCode()方法。其规则是如果两个对 象通过equals方法比较返回true时,其hashCode也应该相同。另外,对象中用作equals比较标准的属性,都应该用来计算 hashCode的值。

LinkedHashSet:
LinkedHashSet集合同样是根据元素的hashCode值来决定元素的存储位置,但是它同时使用链表维护元素的次序。这样使得元素看起 来像是以插入顺序保存的,也就是说,当遍历该集合时候,LinkedHashSet将会以元素的添加顺序访问集合的元素。
LinkedHashSet在迭代访问Set中的全部元素时,性能比HashSet好,但是插入时性能稍微逊色于HashSet

TreeSet类:
TreeSet是SortedSet接口的唯一实现类,TreeSet可以确保集合元素处于排序状态。TreeSet支持两种排序方式,自然排序定制排序,其中自然排序为默认的排序方式。向TreeSet中加入的应该是同一个类的对象。
TreeSet判断两个对象不相等的方式是两个对象通过equals方法返回false,或者通过CompareTo方法比较没有返回0
自然排序:
自然排序使用要排序元素的CompareTo(Object obj)方法来比较元素之间大小关系,然后将元素按照升序排列。
Java提供了一个Comparable接口,该接口里定义了一个compareTo(Object obj)方法,该方法返回一个整数值,实现了该接口的对象就可以比较大小。
obj1.compareTo(obj2)方法如果返回0,则说明被比较的两个对象相等,如果返回一个正数,则表明obj1大于obj2,如果是 负数,则表明obj1小于obj2。
如果我们将两个对象的equals方法总是返回true,则这两个对象的compareTo方法返回应该返回0
定制排序:
自然排序是根据集合元素的大小,以升序排列,如果要定制排序,应该使用Comparator接口,实现 int compare(T o1,T o2)方法




——————————————————————————————————————————————

五、LinkedHashMap源码分析

http://zhangshixi.iteye.com/blog/673143

1.    HashSet概述:

   HashSet实现Set接口,由哈希表(实际上是一个HashMap实例)支持。它不保证set 的迭代顺序;特别是它不保证该顺序恒久不变。此类允许使用null元素。

2.    HashSet的实现:

   对于HashSet而言,它是基于HashMap实现的,因此HashSet 的实现比较简单,相关HashSet的操作,基本上都是直接调用底层HashMap的相关方法来完成, HashSet的源代码如下:

  1. public class HashSet<E>  
  2.     extends AbstractSet<E>  
  3.     implements Set<E>, Cloneable, java.io.Serializable  
  4. {  
  5.     static final long serialVersionUID = -5024744406713321676L;  
  6.   
  7.     // 底层使用HashMap来保存HashSet中所有元素。  
  8.     private transient HashMap<E,Object> map;  
  9.       
  10.     // 定义一个虚拟的Object对象作为HashMap的value,将此对象定义为static final。  
  11.     private static final Object PRESENT = new Object();  
  12.   
  13.     /** 
  14.      * 默认的无参构造器,构造一个空的HashSet。 
  15.      *  
  16.      * 实际底层会初始化一个空的HashMap,并使用默认初始容量为16和加载因子0.75。 
  17.      */  
  18.     public HashSet() {  
  19.     map = new HashMap<E,Object>();  
  20.     }  
  21.   
  22.     /** 
  23.      * 构造一个包含指定collection中的元素的新set。 
  24.      * 
  25.      * 实际底层使用默认的加载因子0.75和足以包含指定 
  26.      * collection中所有元素的初始容量来创建一个HashMap。 
  27.      * @param c 其中的元素将存放在此set中的collection。 
  28.      */  
  29.     public HashSet(Collection<? extends E> c) {  
  30.     map = new HashMap<E,Object>(Math.max((int) (c.size()/.75f) + 116));  
  31.     addAll(c);  
  32.     }  
  33.   
  34.     /** 
  35.      * 以指定的initialCapacity和loadFactor构造一个空的HashSet。 
  36.      * 
  37.      * 实际底层以相应的参数构造一个空的HashMap。 
  38.      * @param initialCapacity 初始容量。 
  39.      * @param loadFactor 加载因子。 
  40.      */  
  41.     public HashSet(int initialCapacity, float loadFactor) {  
  42.     map = new HashMap<E,Object>(initialCapacity, loadFactor);  
  43.     }  
  44.   
  45.     /** 
  46.      * 以指定的initialCapacity构造一个空的HashSet。 
  47.      * 
  48.      * 实际底层以相应的参数及加载因子loadFactor为0.75构造一个空的HashMap。 
  49.      * @param initialCapacity 初始容量。 
  50.      */  
  51.     public HashSet(int initialCapacity) {  
  52.     map = new HashMap<E,Object>(initialCapacity);  
  53.     }  
  54.   
  55.     /** 
  56.      * 以指定的initialCapacity和loadFactor构造一个新的空链接哈希集合。 
  57.      * 此构造函数为包访问权限,不对外公开,实际只是是对LinkedHashSet的支持。 
  58.      * 
  59.      * 实际底层会以指定的参数构造一个空LinkedHashMap实例来实现。 
  60.      * @param initialCapacity 初始容量。 
  61.      * @param loadFactor 加载因子。 
  62.      * @param dummy 标记。 
  63.      */  
  64.     HashSet(int initialCapacity, float loadFactor, boolean dummy) {  
  65.     map = new LinkedHashMap<E,Object>(initialCapacity, loadFactor);  
  66.     }  
  67.   
  68.     /** 
  69.      * 返回对此set中元素进行迭代的迭代器。返回元素的顺序并不是特定的。 
  70.      *  
  71.      * 底层实际调用底层HashMap的keySet来返回所有的key。 
  72.      * 可见HashSet中的元素,只是存放在了底层HashMap的key上, 
  73.      * value使用一个static final的Object对象标识。 
  74.      * @return 对此set中元素进行迭代的Iterator。 
  75.      */  
  76.     public Iterator<E> iterator() {  
  77.     return map.keySet().iterator();  
  78.     }  
  79.   
  80.     /** 
  81.      * 返回此set中的元素的数量(set的容量)。 
  82.      * 
  83.      * 底层实际调用HashMap的size()方法返回Entry的数量,就得到该Set中元素的个数。 
  84.      * @return 此set中的元素的数量(set的容量)。 
  85.      */  
  86.     public int size() {  
  87.     return map.size();  
  88.     }  
  89.   
  90.     /** 
  91.      * 如果此set不包含任何元素,则返回true。 
  92.      * 
  93.      * 底层实际调用HashMap的isEmpty()判断该HashSet是否为空。 
  94.      * @return 如果此set不包含任何元素,则返回true。 
  95.      */  
  96.     public boolean isEmpty() {  
  97.     return map.isEmpty();  
  98.     }  
  99.   
  100.     /** 
  101.      * 如果此set包含指定元素,则返回true。 
  102.      * 更确切地讲,当且仅当此set包含一个满足(o==null ? e==null : o.equals(e)) 
  103.      * 的e元素时,返回true。 
  104.      * 
  105.      * 底层实际调用HashMap的containsKey判断是否包含指定key。 
  106.      * @param o 在此set中的存在已得到测试的元素。 
  107.      * @return 如果此set包含指定元素,则返回true。 
  108.      */  
  109.     public boolean contains(Object o) {  
  110.     return map.containsKey(o);  
  111.     }  
  112.   
  113.     /** 
  114.      * 如果此set中尚未包含指定元素,则添加指定元素。 
  115.      * 更确切地讲,如果此 set 没有包含满足(e==null ? e2==null : e.equals(e2)) 
  116.      * 的元素e2,则向此set 添加指定的元素e。 
  117.      * 如果此set已包含该元素,则该调用不更改set并返回false。 
  118.      * 
  119.      * 底层实际将将该元素作为key放入HashMap。 
  120.      * 由于HashMap的put()方法添加key-value对时,当新放入HashMap的Entry中key 
  121.      * 与集合中原有Entry的key相同(hashCode()返回值相等,通过equals比较也返回true), 
  122.      * 新添加的Entry的value会将覆盖原来Entry的value,但key不会有任何改变, 
  123.      * 因此如果向HashSet中添加一个已经存在的元素时,新添加的集合元素将不会被放入HashMap中, 
  124.      * 原来的元素也不会有任何改变,这也就满足了Set中元素不重复的特性。 
  125.      * @param e 将添加到此set中的元素。 
  126.      * @return 如果此set尚未包含指定元素,则返回true。 
  127.      */  
  128.     public boolean add(E e) {  
  129.     return map.put(e, PRESENT)==null;  
  130.     }  
  131.   
  132.     /** 
  133.      * 如果指定元素存在于此set中,则将其移除。 
  134.      * 更确切地讲,如果此set包含一个满足(o==null ? e==null : o.equals(e))的元素e, 
  135.      * 则将其移除。如果此set已包含该元素,则返回true 
  136.      * (或者:如果此set因调用而发生更改,则返回true)。(一旦调用返回,则此set不再包含该元素)。 
  137.      * 
  138.      * 底层实际调用HashMap的remove方法删除指定Entry。 
  139.      * @param o 如果存在于此set中则需要将其移除的对象。 
  140.      * @return 如果set包含指定元素,则返回true。 
  141.      */  
  142.     public boolean remove(Object o) {  
  143.     return map.remove(o)==PRESENT;  
  144.     }  
  145.   
  146.     /** 
  147.      * 从此set中移除所有元素。此调用返回后,该set将为空。 
  148.      * 
  149.      * 底层实际调用HashMap的clear方法清空Entry中所有元素。 
  150.      */  
  151.     public void clear() {  
  152.     map.clear();  
  153.     }  
  154.   
  155.     /** 
  156.      * 返回此HashSet实例的浅表副本:并没有复制这些元素本身。 
  157.      * 
  158.      * 底层实际调用HashMap的clone()方法,获取HashMap的浅表副本,并设置到HashSet中。 
  159.      */  
  160.     public Object clone() {  
  161.         try {  
  162.             HashSet<E> newSet = (HashSet<E>) super.clone();  
  163.             newSet.map = (HashMap<E, Object>) map.clone();  
  164.             return newSet;  
  165.         } catch (CloneNotSupportedException e) {  
  166.             throw new InternalError();  
  167.         }  
  168.     }  
  169. }  


现在来看一下HashSet的add方法
  1. public boolean add(E e) {
  2.     return map.put(e, PRESENT)==null;
  3.     }
正如我们看到的,add方法调用了HashMap的put方法。这里我们看到了之前的成员变量PRESENT,即,我们存入到HashSet的对象作为key传给了map,而我们 不关心这个key对应的value,所以才定义了一个静态final的object对象。
而保证集合中元素的唯一性的工作,也就顺理成章的交给了HashMap对完成。


 ——————————————————————————————————————————————

六、实例:

1、实例1:

由源程序可以看出,HashSet 的实现其实非常简单,它只是封装了一个 HashMap 对象来存储所有的集合元素,所有放入 HashSet 中的集合元素实际上由 HashMap 的 key 来保存,而 HashMap 的 value 则存储了一个 PRESENT,它是一个静态的 Object 对象。 


HashSet 的绝大部分方法都是通过调用 HashMap 的方法来实现的,因此 HashSet 和 HashMap 两个集合在实现本质上是相同的。 
掌握上面理论知识之后,接下来看一个示例程序,测试一下自己是否真正掌握了 HashMap 和 HashSet 集合的功能。 
Java代码:
public class Name {  
    private String first;   
    private String last;   
    public Name(String first, String last)   {   
        this.first = first;   
        this.last = last;   
    }   
 public boolean equals(Object o)   {   
        if (this == o)    {   
            return true;   
        }   

    if (o.getClass() == Name.class)    {   
            Name n = (Name)o;   
            return n.first.equals(first)   
                && n.last.equals(last);   
        }   
        return false;   
    }   
}  
  
public class HashSetTest  {  
    public static void main(String[] args)    {   
        Set<Name> s = new HashSet<Name>();  
        s.add(new Name("abc", "123"));  
        System.out.println(  
            s.contains(new Name("abc", "123")));  
    }  
}   
上面程序中向 HashSet 里添加了一个 new Name("abc", "123") 对象之后,立即通过程序判断该 HashSet 是否包含一个 new Name("abc", "123") 对象。粗看上去,很容易以为该程序会输出 true。 
实际运行上面程序将看到程序输出 false,这是因为 HashSet 判断两个对象相等的标准除了要求通过 equals() 方法比较返回 true 之外,还要求两个对象的 hashCode() 返回值相等。而上面程序没有重写 Name 类的 hashCode() 方法两个 Name 对象的 hashCode() 返回值并不相同,因此 HashSet 会把它们当成 2 个对象处理,因此程序返回 false。 
由此可见,当我们试图把某个类的对象当成 HashMap 的 key,或试图将这个类的对象放入 HashSet 中保存时,重写该类的 equals(Object obj) 方法和 hashCode() 方法很重要,而且这两个方法的返回值必须保持一致:当该类的两个的 hashCode() 返回值相同时,它们通过 equals() 方法比较也应该返回 true。通常来说,所有参与计算 hashCode() 返回值的关键属性,都应该用于作为 equals() 比较的标准。 
如下程序就正确重写了 Name 类的 hashCode() 和 equals() 方法,程序如下: 
Java代码:
public class Name   {   
    private String first;  
    private String last;  
    public Name(String first, String last) {   
        this.first = first;   
        this.last = last;   
    }   
    // 根据 first 判断两个 Name 是否相等  
    public boolean equals(Object o)  {   
        if (this == o) {   
            return true;   
        }   
        if (o.getClass() == Name.class) {   
            Name n = (Name)o;   
            return n.first.equals(first);   
        }   
        return false;   
    }      
    // 根据 first 计算 Name 对象的 hashCode() 返回值  
    public int hashCode() {   
        return first.hashCode();   
    }  
    public String toString()  {   
        return "Name[first=" + first + ", last=" + last + "]";   
    }   
 }   
   
 public class HashSetTest2  {   
    public static void main(String[] args)  {   
        HashSet<Name> set = new HashSet<Name>();   
        set.add(new Name("abc" , "123"));   

        set.add(new Name("abc" , "456"));   
        System.out.println(set);   
    }   
}  
上面程序中提供了一个 Name 类,该 Name 类重写了 equals() 和 toString() 两个方法,这两个方法都是根据 Name 类的 first 实例变量来判断的,当两个 Name 对象的 first 实例变量相等时,这两个 Name 对象的 hashCode() 返回值也相同,通过 equals() 比较也会返回 true。 
程序主方法先将第一个 Name 对象添加到 HashSet 中,该 Name 对象的 first 实例变量值为"abc",接着程序再次试图将一个 first 为"abc"的 Name 对象添加到 HashSet 中,很明显,此时没法将新的 Name 对象添加到该 HashSet 中,因为此处试图添加的 Name 对象的 first 也是" abc",HashSet 会判断此处新增的 Name 对象与原有的 Name 对象相同,因此无法添加进入,程序在①号代码处输出 set 集合时将看到该集合里只包含一个 Name 对象,就是第一个、last 为"123"的 Name 对象。





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值