Java 集合系列 16 HashSet 第1 部分 HashSet介绍 第2 部分 HashSet数据结构 第3 部分 源码分析

java 集合系列目录:

Java 集合系列 01 总体框架

Java 集合系列 02 Collection架构

Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例

Java 集合系列 04 LinkedList详细介绍(源码解析)和使用示例 

Java 集合系列 05 Vector详细介绍(源码解析)和使用示例

Java 集合系列 06 Stack详细介绍(源码解析)和使用示例

Java 集合系列 07 List总结(LinkedList, ArrayList等使用场景和性能分析)

Java 集合系列 08 Map架构

Java 集合系列 09 HashMap详细介绍(源码解析)和使用示例

Java 集合系列 10 Hashtable详细介绍(源码解析)和使用示例

Java 集合系列 11 hashmap 和 hashtable 的区别

Java 集合系列 12 TreeMap

Java 集合系列 13 WeakHashMap

Java 集合系列 14 hashCode

Java 集合系列 15 Map总结

Java 集合系列 16 HashSet

Java 集合系列 17 TreeSet

概述

第1 部分 HashSet介绍 

  1.1 HashSet 简介

  1.2 HashSet的构造函数

  1.3 HashSet的主要API

第2 部分 HashSet数据结构

第3 部分 源码分析

1.1 HashSet 简介

  HashSet 是一个没有重复元素的集合。它是由HashMap实现的,不保证元素的顺序,而且HashSet允许使用 null 元素

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

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

1.2 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)

1.3 HashSet的主要API

 boolean add(E e) 
          如果此 set 中尚未包含指定元素,则添加指定元素。 
 void clear() 
          从此 set 中移除所有元素。 
 Object clone() 
          返回此 HashSet 实例的浅表副本:并没有复制这些元素本身。 
 boolean contains(Object o) 
          如果此 set 包含指定元素,则返回 trueboolean isEmpty() 
          如果此 set 不包含任何元素,则返回 true。 
 Iterator<E> iterator() 
          返回对此 set 中元素进行迭代的迭代器。 
 boolean remove(Object o) 
          如果指定元素存在于此 set 中,则将其移除。 
 int size() 
          返回此 set 中的元素的数量(set 的容量)。 

第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关系如下图:

Java 集合系列 16 HashSet
第1 部分 HashSet介绍
第2 部分 HashSet数据结构
第3 部分 源码分析

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

第3 部分 源码分析

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