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(Collectionextends 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     iterator()
boolean         remove(Object object)
int             size()
复制代码

 

第2部分 HashSet数据结构

HashSet的继承关系如下:

复制代码
java.lang.Object
   ↳     java.util.AbstractCollection
         ↳     java.util.AbstractSet
               ↳     java.util.HashSet

public class HashSet
    extends AbstractSet
    implements Set, 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
  4     extends AbstractSet
  5     implements Set, Cloneable, java.io.Serializable
  6 {
  7     static final long serialVersionUID = -5024744406713321676L;
  8 
  9     // HashSet是通过map(HashMap对象)保存内容的
 10     private transient HashMap 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();
 21     }
 22 
 23     // 带集合的构造函数
 24     public HashSet(Collectionextends 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(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(initialCapacity, loadFactor);
 44     }
 45 
 46     // 指定HashSet初始容量的构造函数
 47     public HashSet(int initialCapacity) {
 48         map = new HashMap(initialCapacity);
 49     }
 50 
 51     HashSet(int initialCapacity, float loadFactor, boolean dummy) {
 52         map = new LinkedHashMap(initialCapacity, loadFactor);
 53     }
 54 
 55     // 返回HashSet的迭代器
 56     public Iterator 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 newSet = (HashSet) super.clone();
 91             newSet.map = (HashMap) 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(capacity, loadFactor) :
130                new HashMap(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) {
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\n", 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\n", 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\n", 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

复制代码
 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\n", set.size());
32 
33         // 判断HashSet是否包含某个值
34         System.out.printf("HashSet contains a :%s\n", set.contains("a"));
35         System.out.printf("HashSet contains g :%s\n", 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\n", 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\n", 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\n", retainset);
64 
65 
66         // 遍历HashSet
67         for(Iterator iterator = set.iterator();
68                iterator.hasNext(); ) 
69             System.out.printf("iterator : %s\n", iterator.next());
70 
71         // 清空HashSet
72         set.clear();
73 
74         // 输出HashSet是否为空
75         System.out.printf("%s\n", set.isEmpty()?"set is empty":"set is not empty");
76     }
77 
78 }
复制代码

运行结果: 

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

你可能感兴趣的:(Java,集合系列)