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


    原文网址:http://www.cnblogs.com/skywang12345/p/3311252.html

    概要

    这一章,我们对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源码代码作出分析。

    package java.util;

    public class HashSet<E>
    extends AbstractSet<E>
    implements Set<E>, Cloneable, java.io.Serializable
    {
    static final long serialVersionUID = -5024744406713321676L;

    // HashSet是通过map(HashMap对象)保存内容的
    private transient HashMap<E,Object> map;

    // PRESENT是向map中插入key-value对应的value
    // 因为HashSet中只需要用到key,而HashMap是key-value键值对;
    // 所以,向map中添加键值对时,键值对的值固定是PRESENT
    private static final Object PRESENT = new Object();

    // 默认构造函数
    public HashSet() {
    // 调用HashMap的默认构造函数,创建map
    map = new HashMap<E,Object>();
    }

    // 带集合的构造函数
    public HashSet(Collection<? extends E> c) {
    // 创建map。
    // 为什么要调用Math.max((int) (c.size()/.75f) + 1, 16),从 (c.size()/.75f) + 1 和 16 中选择一个比较大的树呢?
    // 首先,说明(c.size()/.75f) + 1
    // 因为从HashMap的效率(时间成本和空间成本)考虑,HashMap的加载因子是0.75。
    // 当HashMap的“阈值”(阈值=HashMap总的大小*加载因子) < “HashMap实际大小”时,
    // 就需要将HashMap的容量翻倍。
    // 所以,(c.size()/.75f) + 1 计算出来的正好是总的空间大小。
    // 接下来,说明为什么是 16 。
    // HashMap的总的大小,必须是2的指数倍。若创建HashMap时,指定的大小不是2的指数倍;
    // HashMap的构造函数中也会重新计算,找出比“指定大小”大的最小的2的指数倍的数。
    // 所以,这里指定为16是从性能考虑。避免重复计算。
    map = new HashMap<E,Object>(Math.max((int) (c.size()/.75f) + 1, 16));
    // 将集合(c)中的全部元素添加到HashSet中
    addAll(c);
    }

    // 指定HashSet初始容量和加载因子的构造函数
    public HashSet(int initialCapacity, float loadFactor) {
    map = new HashMap<E,Object>(initialCapacity, loadFactor);
    }

    // 指定HashSet初始容量的构造函数
    public HashSet(int initialCapacity) {
    map = new HashMap<E,Object>(initialCapacity);
    }

    HashSet(int initialCapacity, float loadFactor, boolean dummy) {
    map = new LinkedHashMap<E,Object>(initialCapacity, loadFactor);
    }

    // 返回HashSet的迭代器
    public Iterator<E> iterator() {
    // 实际上返回的是HashMap的“key集合的迭代器”
    return map.keySet().iterator();
    }

    public int size() {
    return map.size();
    }

    public boolean isEmpty() {
    return map.isEmpty();
    }

    public boolean contains(Object o) {
    return map.containsKey(o);
    }

    // 将元素(e)添加到HashSet中
    public boolean add(E e) {
    return map.put(e, PRESENT)==null;
    }

    // 删除HashSet中的元素(o)
    public boolean remove(Object o) {
    return map.remove(o)==PRESENT;
    }

    public void clear() {
    map.clear();
    }

    // 克隆一个HashSet,并返回Object对象
    public Object clone() {
    try {
    HashSet<E> newSet = (HashSet<E>) super.clone();
    newSet.map = (HashMap<E, Object>) map.clone();
    return newSet;
    } catch (CloneNotSupportedException e) {
    throw new InternalError();
    }
    }

    // java.io.Serializable的写入函数
    // 将HashSet的“总的容量,加载因子,实际容量,所有的元素”都写入到输出流中
    private void writeObject(java.io.ObjectOutputStream s)
    throws java.io.IOException {
    // Write out any hidden serialization magic
    s.defaultWriteObject();

    // Write out HashMap capacity and load factor
    s.writeInt(map.capacity());
    s.writeFloat(map.loadFactor());

    // Write out size
    s.writeInt(map.size());

    // Write out all elements in the proper order.
    for (Iterator i=map.keySet().iterator(); i.hasNext(); )
    s.writeObject(i.next());
    }


    // java.io.Serializable的读取函数
    // 将HashSet的“总的容量,加载因子,实际容量,所有的元素”依次读出
    private void readObject(java.io.ObjectInputStream s)
    throws java.io.IOException, ClassNotFoundException {
    // Read in any hidden serialization magic
    s.defaultReadObject();

    // Read in HashMap capacity and load factor and create backing HashMap
    int capacity = s.readInt();
    float loadFactor = s.readFloat();
    map = (((HashSet)this) instanceof LinkedHashSet ?
    new LinkedHashMap<E,Object>(capacity, loadFactor) :
    new HashMap<E,Object>(capacity, loadFactor));

    // Read in size
    int size = s.readInt();

    // Read in all elements in the proper order.
    for (int i=0; i<size; i++) {
    E e = (E) s.readObject();
    map.put(e, PRESENT);
    }
    }
    }

    复制代码
      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 }
    复制代码

    说明: 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的遍历测试程序如下: 

    import java.util.Random;
    import java.util.Iterator;
    import java.util.HashSet;

    /*
    * @desc 介绍HashSet遍历方法
    *
    * @author skywang
    */
    public class HashSetIteratorTest {

    public static void main(String[] args) {
    // 新建HashSet
    HashSet set = new HashSet();

    // 添加元素 到HashSet中
    for (int i=0; i<5; i++)
    set.add(""+i);

    // 通过Iterator遍历HashSet
    iteratorHashSet(set) ;

    // 通过for-each遍历HashSet
    foreachHashSet(set);
    }

    /*
    * 通过Iterator遍历HashSet。推荐方式
    */
    private static void iteratorHashSet(HashSet set) {
    for(Iterator iterator = set.iterator();
    iterator.hasNext(); ) {
    System.out.printf("iterator : %s ", iterator.next());
    }
    }

    /*
    * 通过for-each遍历HashSet。不推荐!此方法需要先将Set转换为数组
    */
    private static void foreachHashSet(HashSet set) {
    String[] arr = (String[])set.toArray(new String[0]);
    for (String str:arr)
    System.out.printf("for each : %s ", str);
    }
    }

    复制代码
     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 }
    复制代码

    运行结果: 

    复制代码
    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

     

    import java.util.Iterator;
    import java.util.HashSet;

    /*
    * @desc HashSet常用API的使用。
    *
    * @author skywang
    */
    public class HashSetTest {

    public static void main(String[] args) {
    // HashSet常用API
    testHashSetAPIs() ;
    }

    /*
    * HashSet除了iterator()和add()之外的其它常用API
    */
    private static void testHashSetAPIs() {
    // 新建HashSet
    HashSet set = new HashSet();

    // 将元素添加到Set中
    set.add("a");
    set.add("b");
    set.add("c");
    set.add("d");
    set.add("e");

    // 打印HashSet的实际大小
    System.out.printf("size : %d ", set.size());

    // 判断HashSet是否包含某个值
    System.out.printf("HashSet contains a :%s ", set.contains("a"));
    System.out.printf("HashSet contains g :%s ", set.contains("g"));

    // 删除HashSet中的“e”
    set.remove("e");

    // 将Set转换为数组
    String[] arr = (String[])set.toArray(new String[0]);
    for (String str:arr)
    System.out.printf("for each : %s ", str);

    // 新建一个包含b、c、f的HashSet
    HashSet otherset = new HashSet();
    otherset.add("b");
    otherset.add("c");
    otherset.add("f");

    // 克隆一个removeset,内容和set一模一样
    HashSet removeset = (HashSet)set.clone();
    // 删除“removeset中,属于otherSet的元素”
    removeset.removeAll(otherset);
    // 打印removeset
    System.out.printf("removeset : %s ", removeset);

    // 克隆一个retainset,内容和set一模一样
    HashSet retainset = (HashSet)set.clone();
    // 保留“retainset中,属于otherSet的元素”
    retainset.retainAll(otherset);
    // 打印retainset
    System.out.printf("retainset : %s ", retainset);


    // 遍历HashSet
    for(Iterator iterator = set.iterator();
    iterator.hasNext(); )
    System.out.printf("iterator : %s ", iterator.next());

    // 清空HashSet
    set.clear();

    // 输出HashSet是否为空
    System.out.printf("%s ", set.isEmpty()?"set is empty":"set is not empty");
    }

    }

    运行结果: 

    复制代码
    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
    复制代码
    img 生活的悲欢离合永远在地平线以外,而眺望是一种青春的姿态...
    PS.文章是笔者分享的学习笔记,若你觉得可以、还行、过得去、甚至不太差的话,可以“推荐”一下的哦。就此谢过!
     
  • 相关阅读:
    字符,字节和编码
    Linux网络参数和ifconfig
    默认网关 网关 子网掩码 广播地址
    S.M.A.R.T.记录几块ssd硬盘
    linux 别名
    echo 输出颜色
    Linux:echo命令详解
    centos下安装mongodb 通过shell脚本
    linux查看登录用户
    linux wget指定下载目录和重命名
  • 原文地址:https://www.cnblogs.com/wi100sh/p/4487089.html
Copyright © 2020-2023  润新知