• Java 集合系列16之 HashSet详细介绍(源码解析)和使用示例


    概要

    这一章,我们对HashSet进行学习。
    我们先对HashSet有个整体认识,然后再学习它的源码,最后再通过实例来学会使用HashSet。内容包括:
    第1部分 HashSet介绍
    第2部分 HashSet数据结构
    第3部分 HashSet源码解析(基于JDK1.6.0_45)
    第4部分 HashSet遍历方式
    第5部分 HashSet示例

    转载请注明出处:http://www.cnblogs.com/skywang12345/p/3311252.html

     

    第1部分 HashSet介绍

    HashSet 简介

    HashSet 是一个没有重复元素的集合
    它是由HashMap实现的,不保证元素的顺序,而且HashSet允许使用 null 元素
    HashSet是非同步的。如果多个线程同时访问一个哈希 set,而其中至少一个线程修改了该 set,那么它必须 保持外部同步。这通常是通过对自然封装该 set 的对象执行同步操作来完成的。如果不存在这样的对象,则应该使用 Collections.synchronizedSet 方法来“包装” set。最好在创建时完成这一操作,以防止对该 set 进行意外的不同步访问:

    Set s = Collections.synchronizedSet(new HashSet(...));

    HashSet通过iterator()返回的迭代器是fail-fast的。

    HashSet的构造函数

     // 默认构造函数
    public HashSet() 
    
    // 带集合的构造函数
    public HashSet(Collection<? extends E> c) 
    
    // 指定HashSet初始容量和加载因子的构造函数
    public HashSet(int initialCapacity, float loadFactor) 
    
    // 指定HashSet初始容量的构造函数
    public HashSet(int initialCapacity) 
    
    // 指定HashSet初始容量和加载因子的构造函数,dummy没有任何作用
    HashSet(int initialCapacity, float loadFactor, boolean dummy) 

    HashSet的主要API

    boolean         add(E object)
    void            clear()
    Object          clone()
    boolean         contains(Object object)
    boolean         isEmpty()
    Iterator<E>     iterator()
    boolean         remove(Object object)
    int             size()

    第2部分 HashSet数据结构

    HashSet的继承关系如下:

    java.lang.Object
       ↳     java.util.AbstractCollection<E>
             ↳     java.util.AbstractSet<E>
                   ↳     java.util.HashSet<E>
    
    public class HashSet<E>
        extends AbstractSet<E>
        implements Set<E>, Cloneable, java.io.Serializable { }

    HashSet与Map关系如下图:

    从图中可以看出:
    (01) HashSet继承于AbstractSet,并且实现了Set接口。
    (02) HashSet的本质是一个"没有重复元素"的集合,它是通过HashMap实现的。HashSet中含有一个"HashMap类型的成员变量"map,HashSet的操作函数,实际上都是通过map实现的。

    第3部分 HashSet源码解析(基于JDK1.6.0_45)

    为了更了解HashSet的原理,下面对HashSet源码代码作出分析。

      1 package java.util;
      2 
      3 public class HashSet<E>
      4     extends AbstractSet<E>
      5     implements Set<E>, Cloneable, java.io.Serializable
      6 {
      7     static final long serialVersionUID = -5024744406713321676L;
      8 
      9     // HashSet是通过map(HashMap对象)保存内容的
     10     private transient HashMap<E,Object> map;
     11 
     12     // PRESENT是向map中插入key-value对应的value
     13     // 因为HashSet中只需要用到key,而HashMap是key-value键值对;
     14     // 所以,向map中添加键值对时,键值对的值固定是PRESENT
     15     private static final Object PRESENT = new Object();
     16 
     17     // 默认构造函数
     18     public HashSet() {
     19         // 调用HashMap的默认构造函数,创建map
     20         map = new HashMap<E,Object>();
     21     }
     22 
     23     // 带集合的构造函数
     24     public HashSet(Collection<? extends E> c) {
     25         // 创建map。
     26         // 为什么要调用Math.max((int) (c.size()/.75f) + 1, 16),从 (c.size()/.75f) + 1 和 16 中选择一个比较大的树呢?        
     27         // 首先,说明(c.size()/.75f) + 1
     28         //   因为从HashMap的效率(时间成本和空间成本)考虑,HashMap的加载因子是0.75。
     29         //   当HashMap的“阈值”(阈值=HashMap总的大小*加载因子) < “HashMap实际大小”时,
     30         //   就需要将HashMap的容量翻倍。
     31         //   所以,(c.size()/.75f) + 1 计算出来的正好是总的空间大小。
     32         // 接下来,说明为什么是 16 。
     33         //   HashMap的总的大小,必须是2的指数倍。若创建HashMap时,指定的大小不是2的指数倍;
     34         //   HashMap的构造函数中也会重新计算,找出比“指定大小”大的最小的2的指数倍的数。
     35         //   所以,这里指定为16是从性能考虑。避免重复计算。
     36         map = new HashMap<E,Object>(Math.max((int) (c.size()/.75f) + 1, 16));
     37         // 将集合(c)中的全部元素添加到HashSet中
     38         addAll(c);
     39     }
     40 
     41     // 指定HashSet初始容量和加载因子的构造函数
     42     public HashSet(int initialCapacity, float loadFactor) {
     43         map = new HashMap<E,Object>(initialCapacity, loadFactor);
     44     }
     45 
     46     // 指定HashSet初始容量的构造函数
     47     public HashSet(int initialCapacity) {
     48         map = new HashMap<E,Object>(initialCapacity);
     49     }
     50 
     51     HashSet(int initialCapacity, float loadFactor, boolean dummy) {
     52         map = new LinkedHashMap<E,Object>(initialCapacity, loadFactor);
     53     }
     54 
     55     // 返回HashSet的迭代器
     56     public Iterator<E> iterator() {
     57         // 实际上返回的是HashMap的“key集合的迭代器”
     58         return map.keySet().iterator();
     59     }
     60 
     61     public int size() {
     62         return map.size();
     63     }
     64 
     65     public boolean isEmpty() {
     66         return map.isEmpty();
     67     }
     68 
     69     public boolean contains(Object o) {
     70         return map.containsKey(o);
     71     }
     72 
     73     // 将元素(e)添加到HashSet中
     74     public boolean add(E e) {
     75         return map.put(e, PRESENT)==null;
     76     }
     77 
     78     // 删除HashSet中的元素(o)
     79     public boolean remove(Object o) {
     80         return map.remove(o)==PRESENT;
     81     }
     82 
     83     public void clear() {
     84         map.clear();
     85     }
     86 
     87     // 克隆一个HashSet,并返回Object对象
     88     public Object clone() {
     89         try {
     90             HashSet<E> newSet = (HashSet<E>) super.clone();
     91             newSet.map = (HashMap<E, Object>) map.clone();
     92             return newSet;
     93         } catch (CloneNotSupportedException e) {
     94             throw new InternalError();
     95         }
     96     }
     97 
     98     // java.io.Serializable的写入函数
     99     // 将HashSet的“总的容量,加载因子,实际容量,所有的元素”都写入到输出流中
    100     private void writeObject(java.io.ObjectOutputStream s)
    101         throws java.io.IOException {
    102         // Write out any hidden serialization magic
    103         s.defaultWriteObject();
    104 
    105         // Write out HashMap capacity and load factor
    106         s.writeInt(map.capacity());
    107         s.writeFloat(map.loadFactor());
    108 
    109         // Write out size
    110         s.writeInt(map.size());
    111 
    112         // Write out all elements in the proper order.
    113         for (Iterator i=map.keySet().iterator(); i.hasNext(); )
    114             s.writeObject(i.next());
    115     }
    116 
    117 
    118     // java.io.Serializable的读取函数
    119     // 将HashSet的“总的容量,加载因子,实际容量,所有的元素”依次读出
    120     private void readObject(java.io.ObjectInputStream s)
    121         throws java.io.IOException, ClassNotFoundException {
    122         // Read in any hidden serialization magic
    123         s.defaultReadObject();
    124 
    125         // Read in HashMap capacity and load factor and create backing HashMap
    126         int capacity = s.readInt();
    127         float loadFactor = s.readFloat();
    128         map = (((HashSet)this) instanceof LinkedHashSet ?
    129                new LinkedHashMap<E,Object>(capacity, loadFactor) :
    130                new HashMap<E,Object>(capacity, loadFactor));
    131 
    132         // Read in size
    133         int size = s.readInt();
    134 
    135         // Read in all elements in the proper order.
    136         for (int i=0; i<size; i++) {
    137             E e = (E) s.readObject();
    138             map.put(e, PRESENT);
    139         }
    140     }
    141 }
    View Code

    说明: HashSet的代码实际上非常简单,通过上面的注释应该很能够看懂。它是通过HashMap实现的,若对HashSet的理解有困难,建议先学习以下HashMap;学完HashMap之后,在学习HashSet就非常容易了。

    第4部分 HashSet遍历方式

    4.1 通过Iterator遍历HashSet

    第一步:根据iterator()获取HashSet的迭代器。
    第二步:遍历迭代器获取各个元素

    // 假设set是HashSet对象
    for(Iterator iterator = set.iterator();
           iterator.hasNext(); ) { 
        iterator.next();
    }   

    4.2 通过for-each遍历HashSet

    第一步:根据toArray()获取HashSet的元素集合对应的数组。
    第二步:遍历数组,获取各个元素。

    // 假设set是HashSet对象,并且set中元素是String类型
    String[] arr = (String[])set.toArray(new String[0]);
    for (String str:arr)
        System.out.printf("for each : %s
    ", str);

    HashSet的遍历测试程序如下: 

     1 import java.util.Random;
     2 import java.util.Iterator;
     3 import java.util.HashSet;
     4 
     5 /*
     6  * @desc 介绍HashSet遍历方法
     7  *
     8  * @author skywang
     9  */
    10 public class HashSetIteratorTest {
    11 
    12     public static void main(String[] args) {
    13         // 新建HashSet
    14         HashSet set = new HashSet();
    15 
    16         // 添加元素 到HashSet中
    17         for (int i=0; i<5; i++)
    18             set.add(""+i);
    19 
    20         // 通过Iterator遍历HashSet
    21         iteratorHashSet(set) ;
    22 
    23         // 通过for-each遍历HashSet
    24         foreachHashSet(set);
    25     }
    26 
    27     /*
    28      * 通过Iterator遍历HashSet。推荐方式
    29      */
    30     private static void iteratorHashSet(HashSet set) {
    31         for(Iterator iterator = set.iterator();
    32                iterator.hasNext(); ) {
    33             System.out.printf("iterator : %s
    ", iterator.next());
    34         }
    35     }
    36 
    37     /*
    38      * 通过for-each遍历HashSet。不推荐!此方法需要先将Set转换为数组
    39      */
    40     private static void foreachHashSet(HashSet set) {
    41         String[] arr = (String[])set.toArray(new String[0]);
    42         for (String str:arr)
    43             System.out.printf("for each : %s
    ", str);
    44     }
    45 }
    View Code

    运行结果 

    iterator : 3
    iterator : 2
    iterator : 1
    iterator : 0
    iterator : 4
    for each : 3
    for each : 2
    for each : 1
    for each : 0
    for each : 4

    第5部分 HashSet示例

    下面我们通过实例学习如何使用HashSet

     1 import java.util.Iterator;
     2 import java.util.HashSet;
     3 
     4 /*
     5  * @desc HashSet常用API的使用。
     6  *
     7  * @author skywang
     8  */
     9 public class HashSetTest {
    10 
    11     public static void main(String[] args) {
    12         // HashSet常用API
    13         testHashSetAPIs() ;
    14     }
    15 
    16     /*
    17      * HashSet除了iterator()和add()之外的其它常用API
    18      */
    19     private static void testHashSetAPIs() {
    20         // 新建HashSet
    21         HashSet set = new HashSet();
    22 
    23         // 将元素添加到Set中
    24         set.add("a");
    25         set.add("b");
    26         set.add("c");
    27         set.add("d");
    28         set.add("e");
    29 
    30         // 打印HashSet的实际大小
    31         System.out.printf("size : %d
    ", set.size());
    32 
    33         // 判断HashSet是否包含某个值
    34         System.out.printf("HashSet contains a :%s
    ", set.contains("a"));
    35         System.out.printf("HashSet contains g :%s
    ", set.contains("g"));
    36 
    37         // 删除HashSet中的“e”
    38         set.remove("e");
    39 
    40         // 将Set转换为数组
    41         String[] arr = (String[])set.toArray(new String[0]);
    42         for (String str:arr)
    43             System.out.printf("for each : %s
    ", str);
    44 
    45         // 新建一个包含b、c、f的HashSet
    46         HashSet otherset = new HashSet();
    47         otherset.add("b");
    48         otherset.add("c");
    49         otherset.add("f");
    50 
    51         // 克隆一个removeset,内容和set一模一样
    52         HashSet removeset = (HashSet)set.clone();
    53         // 删除“removeset中,属于otherSet的元素”
    54         removeset.removeAll(otherset);
    55         // 打印removeset
    56         System.out.printf("removeset : %s
    ", removeset);
    57 
    58         // 克隆一个retainset,内容和set一模一样
    59         HashSet retainset = (HashSet)set.clone();
    60         // 保留“retainset中,属于otherSet的元素”
    61         retainset.retainAll(otherset);
    62         // 打印retainset
    63         System.out.printf("retainset : %s
    ", retainset);
    64 
    65 
    66         // 遍历HashSet
    67         for(Iterator iterator = set.iterator();
    68                iterator.hasNext(); ) 
    69             System.out.printf("iterator : %s
    ", iterator.next());
    70 
    71         // 清空HashSet
    72         set.clear();
    73 
    74         // 输出HashSet是否为空
    75         System.out.printf("%s
    ", set.isEmpty()?"set is empty":"set is not empty");
    76     }
    77 
    78 }
    View Code

    运行结果: 

    size : 5
    HashSet contains a :true
    HashSet contains g :false
    for each : d
    for each : b
    for each : c
    for each : a
    removeset : [d, a]
    retainset : [b, c]
    iterator : d
    iterator : b
    iterator : c
    iterator : a
    set is empty
  • 相关阅读:
    Primeface datatable celleditor rowedit 例子
    JAVA Assert
    POI 日期类型的显示,日期类型存储为Double,数字类型雷同,为了显示为日期格式。。。
    hibernate3支持中文查询
    VC2010 编写DLL并调用;
    android 数据库例子
    VC2010 OPENCV 配置攻略;生成opencv向导
    VC2012 MFC 项目 mfc100.lib 位置
    QQ自动远程连接 JNA
    EXCEL 中 get.cell 函数的参数
  • 原文地址:https://www.cnblogs.com/skywang12345/p/3311252.html
Copyright © 2020-2023  润新知