java 集合系列目录:
Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例
Java 集合系列 04 LinkedList详细介绍(源码解析)和使用示例
Java 集合系列 05 Vector详细介绍(源码解析)和使用示例
Java 集合系列 06 Stack详细介绍(源码解析)和使用示例
Java 集合系列 07 List总结(LinkedList, ArrayList等使用场景和性能分析)
Java 集合系列 09 HashMap详细介绍(源码解析)和使用示例
Java 集合系列 10 Hashtable详细介绍(源码解析)和使用示例
Java 集合系列 11 hashmap 和 hashtable 的区别
概述
第1 部分 HashSet介绍
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 包含指定元素,则返回 true。 boolean 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关系如下图:
从图中可以看出:
(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 }