2017-07-29 16:58:13html
Set能夠理解爲集合,很是相似數據概念中的集合,集合三大特徵:一、肯定性;二、互異性;三、無序性,所以Set實現類也有相似的特徵。java
HashSet繼承自AbstractSet,實現了Set接口,可是其源碼很是少,也很是簡單。內部使用HashMap來存儲數據,數據存儲在HashMap的key中,value都是同一個默認值:python
HashSet的肯定性,也能夠理解爲惟一性,是經過HashMap的put方法來保證的,往HashMap中put數據時,若是key是同樣的,只會替換key對應的value,不會新插入一條數據。因此往HashSet中add相同的元素沒有什麼用,這裏的相同是經過equals方法保證的,具體的在HashMap中細說。golang
簡單粗暴,從HashMap中移除一條數據。ruby
其餘的方法諸如:size()、isEmpty()、contains()、clear()等都徹底委託給了HashMap。須要注意的是:HashSet沒有提供set、get等方法。數據結構
源碼以下:app
1 package java.util; 2 3 import java.io.InvalidObjectException; 4 5 /** 6 * This class implements the <tt>Set</tt> interface, backed by a hash table 7 * (actually a <tt>HashMap</tt> instance). It makes no guarantees as to the 8 * iteration order of the set; in particular, it does not guarantee that the 9 * order will remain constant over time. This class permits the <tt>null</tt> 10 * element. 11 * 12 * <p>This class offers constant time performance for the basic operations 13 * (<tt>add</tt>, <tt>remove</tt>, <tt>contains</tt> and <tt>size</tt>), 14 * assuming the hash function disperses the elements properly among the 15 * buckets. Iterating over this set requires time proportional to the sum of 16 * the <tt>HashSet</tt> instance's size (the number of elements) plus the 17 * "capacity" of the backing <tt>HashMap</tt> instance (the number of 18 * buckets). Thus, it's very important not to set the initial capacity too 19 * high (or the load factor too low) if iteration performance is important. 20 * 21 * <p><strong>Note that this implementation is not synchronized.</strong> 22 * If multiple threads access a hash set concurrently, and at least one of 23 * the threads modifies the set, it <i>must</i> be synchronized externally. 24 * This is typically accomplished by synchronizing on some object that 25 * naturally encapsulates the set. 26 * 27 * If no such object exists, the set should be "wrapped" using the 28 * {@link Collections#synchronizedSet Collections.synchronizedSet} 29 * method. This is best done at creation time, to prevent accidental 30 * unsynchronized access to the set:<pre> 31 * Set s = Collections.synchronizedSet(new HashSet(...));</pre> 32 * 33 * <p>The iterators returned by this class's <tt>iterator</tt> method are 34 * <i>fail-fast</i>: if the set is modified at any time after the iterator is 35 * created, in any way except through the iterator's own <tt>remove</tt> 36 * method, the Iterator throws a {@link ConcurrentModificationException}. 37 * Thus, in the face of concurrent modification, the iterator fails quickly 38 * and cleanly, rather than risking arbitrary, non-deterministic behavior at 39 * an undetermined time in the future. 40 * 41 * <p>Note that the fail-fast behavior of an iterator cannot be guaranteed 42 * as it is, generally speaking, impossible to make any hard guarantees in the 43 * presence of unsynchronized concurrent modification. Fail-fast iterators 44 * throw <tt>ConcurrentModificationException</tt> on a best-effort basis. 45 * Therefore, it would be wrong to write a program that depended on this 46 * exception for its correctness: <i>the fail-fast behavior of iterators 47 * should be used only to detect bugs.</i> 48 * 49 * <p>This class is a member of the 50 * <a href="{@docRoot}/../technotes/guides/collections/index.html"> 51 * Java Collections Framework</a>. 52 * 53 * @param <E> the type of elements maintained by this set 54 * 55 * @author Josh Bloch 56 * @author Neal Gafter 57 * @see Collection 58 * @see Set 59 * @see TreeSet 60 * @see HashMap 61 * @since 1.2 62 */ 63 64 public class HashSet<E> 65 extends AbstractSet<E> 66 implements Set<E>, Cloneable, java.io.Serializable 67 { 68 static final long serialVersionUID = -5024744406713321676L; 69 70 private transient HashMap<E,Object> map; 71 72 // Dummy value to associate with an Object in the backing Map 73 private static final Object PRESENT = new Object(); 74 75 /** 76 * Constructs a new, empty set; the backing <tt>HashMap</tt> instance has 77 * default initial capacity (16) and load factor (0.75). 78 */ 79 public HashSet() { 80 map = new HashMap<>(); 81 } 82 83 /** 84 * Constructs a new set containing the elements in the specified 85 * collection. The <tt>HashMap</tt> is created with default load factor 86 * (0.75) and an initial capacity sufficient to contain the elements in 87 * the specified collection. 88 * 89 * @param c the collection whose elements are to be placed into this set 90 * @throws NullPointerException if the specified collection is null 91 */ 92 public HashSet(Collection<? extends E> c) { 93 map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16)); 94 addAll(c); 95 } 96 97 /** 98 * Constructs a new, empty set; the backing <tt>HashMap</tt> instance has 99 * the specified initial capacity and the specified load factor. 100 * 101 * @param initialCapacity the initial capacity of the hash map 102 * @param loadFactor the load factor of the hash map 103 * @throws IllegalArgumentException if the initial capacity is less 104 * than zero, or if the load factor is nonpositive 105 */ 106 public HashSet(int initialCapacity, float loadFactor) { 107 map = new HashMap<>(initialCapacity, loadFactor); 108 } 109 110 /** 111 * Constructs a new, empty set; the backing <tt>HashMap</tt> instance has 112 * the specified initial capacity and default load factor (0.75). 113 * 114 * @param initialCapacity the initial capacity of the hash table 115 * @throws IllegalArgumentException if the initial capacity is less 116 * than zero 117 */ 118 public HashSet(int initialCapacity) { 119 map = new HashMap<>(initialCapacity); 120 } 121 122 /** 123 * Constructs a new, empty linked hash set. (This package private 124 * constructor is only used by LinkedHashSet.) The backing 125 * HashMap instance is a LinkedHashMap with the specified initial 126 * capacity and the specified load factor. 127 * 128 * @param initialCapacity the initial capacity of the hash map 129 * @param loadFactor the load factor of the hash map 130 * @param dummy ignored (distinguishes this 131 * constructor from other int, float constructor.) 132 * @throws IllegalArgumentException if the initial capacity is less 133 * than zero, or if the load factor is nonpositive 134 */ 135 HashSet(int initialCapacity, float loadFactor, boolean dummy) { 136 map = new LinkedHashMap<>(initialCapacity, loadFactor); 137 } 138 139 /** 140 * Returns an iterator over the elements in this set. The elements 141 * are returned in no particular order. 142 * 143 * @return an Iterator over the elements in this set 144 * @see ConcurrentModificationException 145 */ 146 public Iterator<E> iterator() { 147 return map.keySet().iterator(); 148 } 149 150 /** 151 * Returns the number of elements in this set (its cardinality). 152 * 153 * @return the number of elements in this set (its cardinality) 154 */ 155 public int size() { 156 return map.size(); 157 } 158 159 /** 160 * Returns <tt>true</tt> if this set contains no elements. 161 * 162 * @return <tt>true</tt> if this set contains no elements 163 */ 164 public boolean isEmpty() { 165 return map.isEmpty(); 166 } 167 168 /** 169 * Returns <tt>true</tt> if this set contains the specified element. 170 * More formally, returns <tt>true</tt> if and only if this set 171 * contains an element <tt>e</tt> such that 172 * <tt>(o==null ? e==null : o.equals(e))</tt>. 173 * 174 * @param o element whose presence in this set is to be tested 175 * @return <tt>true</tt> if this set contains the specified element 176 */ 177 public boolean contains(Object o) { 178 return map.containsKey(o); 179 } 180 181 /** 182 * Adds the specified element to this set if it is not already present. 183 * More formally, adds the specified element <tt>e</tt> to this set if 184 * this set contains no element <tt>e2</tt> such that 185 * <tt>(e==null ? e2==null : e.equals(e2))</tt>. 186 * If this set already contains the element, the call leaves the set 187 * unchanged and returns <tt>false</tt>. 188 * 189 * @param e element to be added to this set 190 * @return <tt>true</tt> if this set did not already contain the specified 191 * element 192 */ 193 public boolean add(E e) { 194 return map.put(e, PRESENT)==null; 195 } 196 197 /** 198 * Removes the specified element from this set if it is present. 199 * More formally, removes an element <tt>e</tt> such that 200 * <tt>(o==null ? e==null : o.equals(e))</tt>, 201 * if this set contains such an element. Returns <tt>true</tt> if 202 * this set contained the element (or equivalently, if this set 203 * changed as a result of the call). (This set will not contain the 204 * element once the call returns.) 205 * 206 * @param o object to be removed from this set, if present 207 * @return <tt>true</tt> if the set contained the specified element 208 */ 209 public boolean remove(Object o) { 210 return map.remove(o)==PRESENT; 211 } 212 213 /** 214 * Removes all of the elements from this set. 215 * The set will be empty after this call returns. 216 */ 217 public void clear() { 218 map.clear(); 219 } 220 221 /** 222 * Returns a shallow copy of this <tt>HashSet</tt> instance: the elements 223 * themselves are not cloned. 224 * 225 * @return a shallow copy of this set 226 */ 227 @SuppressWarnings("unchecked") 228 public Object clone() { 229 try { 230 HashSet<E> newSet = (HashSet<E>) super.clone(); 231 newSet.map = (HashMap<E, Object>) map.clone(); 232 return newSet; 233 } catch (CloneNotSupportedException e) { 234 throw new InternalError(e); 235 } 236 } 237 238 /** 239 * Save the state of this <tt>HashSet</tt> instance to a stream (that is, 240 * serialize it). 241 * 242 * @serialData The capacity of the backing <tt>HashMap</tt> instance 243 * (int), and its load factor (float) are emitted, followed by 244 * the size of the set (the number of elements it contains) 245 * (int), followed by all of its elements (each an Object) in 246 * no particular order. 247 */ 248 private void writeObject(java.io.ObjectOutputStream s) 249 throws java.io.IOException { 250 // Write out any hidden serialization magic 251 s.defaultWriteObject(); 252 253 // Write out HashMap capacity and load factor 254 s.writeInt(map.capacity()); 255 s.writeFloat(map.loadFactor()); 256 257 // Write out size 258 s.writeInt(map.size()); 259 260 // Write out all elements in the proper order. 261 for (E e : map.keySet()) 262 s.writeObject(e); 263 } 264 265 /** 266 * Reconstitute the <tt>HashSet</tt> instance from a stream (that is, 267 * deserialize it). 268 */ 269 private void readObject(java.io.ObjectInputStream s) 270 throws java.io.IOException, ClassNotFoundException { 271 // Read in any hidden serialization magic 272 s.defaultReadObject(); 273 274 // Read capacity and verify non-negative. 275 int capacity = s.readInt(); 276 if (capacity < 0) { 277 throw new InvalidObjectException("Illegal capacity: " + 278 capacity); 279 } 280 281 // Read load factor and verify positive and non NaN. 282 float loadFactor = s.readFloat(); 283 if (loadFactor <= 0 || Float.isNaN(loadFactor)) { 284 throw new InvalidObjectException("Illegal load factor: " + 285 loadFactor); 286 } 287 288 // Read size and verify non-negative. 289 int size = s.readInt(); 290 if (size < 0) { 291 throw new InvalidObjectException("Illegal size: " + 292 size); 293 } 294 295 // Set the capacity according to the size and load factor ensuring that 296 // the HashMap is at least 25% full but clamping to maximum capacity. 297 capacity = (int) Math.min(size * Math.min(1 / loadFactor, 4.0f), 298 HashMap.MAXIMUM_CAPACITY); 299 300 // Create backing HashMap 301 map = (((HashSet<?>)this) instanceof LinkedHashSet ? 302 new LinkedHashMap<E,Object>(capacity, loadFactor) : 303 new HashMap<E,Object>(capacity, loadFactor)); 304 305 // Read in all elements in the proper order. 306 for (int i=0; i<size; i++) { 307 @SuppressWarnings("unchecked") 308 E e = (E) s.readObject(); 309 map.put(e, PRESENT); 310 } 311 } 312 313 /** 314 * Creates a <em><a href="Spliterator.html#binding">late-binding</a></em> 315 * and <em>fail-fast</em> {@link Spliterator} over the elements in this 316 * set. 317 * 318 * <p>The {@code Spliterator} reports {@link Spliterator#SIZED} and 319 * {@link Spliterator#DISTINCT}. Overriding implementations should document 320 * the reporting of additional characteristic values. 321 * 322 * @return a {@code Spliterator} over the elements in this set 323 * @since 1.8 324 */ 325 public Spliterator<E> spliterator() { 326 return new HashMap.KeySpliterator<E,Object>(map, 0, -1, 0, 0); 327 } 328 }
LinkedHashSet繼承自HashSet,源碼更少、更簡單,惟一的區別是LinkedHashSet內部使用的是LinkHashMap。這樣作的意義或者好處就是LinkedHashSet中的元素順序是能夠保證的,也就是說遍歷序和插入序是一致的。less
打印日誌如上,HashSet和HashMap都不保證順序,Link**能保證順序。ide
源碼以下:ui
1 public class MainSet { 2 public static void main(String[] args) { 3 Object value = new Object(); 4 HashMap<String, Object> hashMap = new HashMap<>(); 5 HashSet<String> hashSet = new HashSet<>(); 6 LinkedHashMap<String, Object> linkedHashMap = new LinkedHashMap<>(); 7 LinkedHashSet<String> linkedHashSet = new LinkedHashSet<>(); 8 hashSet.add("java"); 9 hashMap.put("java", value); 10 linkedHashSet.add("java"); 11 linkedHashMap.put("java", value); 12 13 hashSet.add("golang"); 14 hashMap.put("golang", value); 15 linkedHashSet.add("golang"); 16 linkedHashMap.put("golang", value); 17 18 hashSet.add("python"); 19 hashMap.put("python", value); 20 linkedHashSet.add("python"); 21 linkedHashMap.put("python", value); 22 23 hashSet.add("ruby"); 24 hashMap.put("ruby", value); 25 linkedHashSet.add("ruby"); 26 linkedHashMap.put("ruby", value); 27 28 hashSet.add("scala"); 29 hashMap.put("scala", value); 30 linkedHashSet.add("scala"); 31 linkedHashMap.put("scala", value); 32 33 hashSet.add("c"); 34 hashMap.put("c", value); 35 linkedHashSet.add("c"); 36 linkedHashMap.put("c", value); 37 38 System.out.println("默認插入序:\njava\tgolang\tpython\truby\tscala\tc"); 39 40 System.out.println(" \nHashSet:-------------------"); 41 for (String str : hashSet) { 42 System.out.print(str + "\t"); 43 } 44 45 System.out.println(" \n\nHashMap:-------------------"); 46 for (Map.Entry<String, Object> entry : hashMap.entrySet()) { 47 System.out.print(entry.getKey() + "\t"); 48 } 49 50 System.out.println(" \n\nLinkedHashSet:-------------------"); 51 for (String str : linkedHashSet) { 52 System.out.print(str + "\t"); 53 } 54 55 System.out.println(" \n\nLinkedHashMap:-------------------"); 56 for (Map.Entry<String, Object> entry : linkedHashMap.entrySet()) { 57 System.out.print(entry.getKey() + "\t"); 58 } 59 } 60 }
如上,咱們明明添加了6個元素,可是table中只有5個,怎麼回事呢?初步猜想應該是「c」元素和其中某一個元素位於同一個bucket,驗證以下:
咱們發現,「java」居然和「c」位於同一個bucket,他倆在同一個鏈表中。惟一的疑惑是:「c」是後加入的元素,按理說應該在鏈表的表頭纔對啊,這個問題還須要探究。
同時這裏也介紹了一種能debug到HashMap內部數據結構的方法,可是須要注意2個問題:
一、須要在AS中設置一下,不然debug看到的信息不是這樣的,以下圖:
二、直接使用table[3].next是不行的, 須要像圖上那樣,些完整的包名才行。
如上所示,設置HashSet以後,一樣能看到以下信息:
看看LinkedHashMap,以下圖,LinkedHashMap中多了head和tail,這是指向表頭、表尾的指針,head指向「java」,tail指向「c」,這和咱們的插入序保持一致,可是實際存儲和以前是同樣的。
LinkedHashSet以下圖: