HashSet ensures that elements are not duplicated?
To be more direct, in fact, the add() method of HashSet calls the put() method of HashMap. We all know that key of Map does not allow duplication, which is actually the true principle that HashSet can ensure that elements are not duplicated.
Take a look at the source code a little bit
Class HashSet /** Let's first look at the implementation of the add method */ public boolean add(E e) { return map.put(e, PRESENT)==null; } /** Some people will ask where PRESENT comes from. */ // Dummy value to associate with an Object in the backing Map private static final Object PRESENT = new Object();
Explain?
There's nothing to explain.
What we can see is that PRESENT, as an unmodifiable object, is passed in as value in the put method, so all we need to pay attention to is the key characteristics of map and the key in map is not allowed to repeat, and only one null value exists.
As to why map's key does not allow duplication, we continue to look at hashMap's put() method.
The source code here is interpreted and reprinted from a blog
Portal: An in-depth analysis of the HashMap principle (based on JDK.8).
public V put(K key, V value) { return putVal(hash(key), key, value, false, true); } //putVal() //If onlyIfAbsent is true, do not change the existing value //If evict is true, the table is in the creation mode final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node<K,V>[] tab; Node<K,V> p; int n, i; //If the table on the trunk is empty and the length is 0, call the resize method to adjust the length of the table if ((tab = table) == null || (n = tab.length) == 0) /* The resize call here is actually the first put to initialize the array. If it is the default constructor, these sentences in resize will be executed: newCap = DEFAULT_INITIAL_CAPACITY; The new capacity equals the default value of 16 newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); threshold = newThr; The critical value is 16*0.75 Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; table = newTab; Assign the new node array to the table, and then return the new Tab If it's a custom constructor, it executes the following in resize: int oldThr = threshold; newCap = oldThr; The new capacity equals threshold, where thresholds are multiples of two, for the reason that A new value is returned when all incoming numbers are passed through the tableSizeFor method. float ft = (float)newCap * loadFactor; newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? (int)ft : Integer.MAX_VALUE); threshold = newThr; The new critical value is equal to (int) (new capacity * load factor) Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; table = newTab; return newTab; */ n = (tab = resize()).length; //Assign the length of the array constructed after calling resize to n if ((p = tab[i = (n - 1) & hash]) == null) //Comparing the array length with the hash value calculated tab[i] = newNode(hash, key, value, null);//Empty position assigns a node object to the i position else { //Location is not empty Node<K,V> e; K k; if (p.hash == hash && // If the old node at this location is exactly the same as the key of the new node ((k = p.key) == key || (key != null && key.equals(k)))) e = p; // Then e=p else if (p instanceof TreeNode) // If p is already an instance of a tree node, it is already a tree here. e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); else { //p is neither exactly the same as the new node nor an instance of treenode for (int binCount = 0; ; ++binCount) { //A Dead Cycle if ((e = p.next) == null) { //e=p.next, if the next point of p is null p.next = newNode(hash, key, value, null); //Point to a new node if (binCount >= TREEIFY_THRESHOLD - 1) // If the list length is greater than or equal to 8 treeifyBin(tab, hash); //Convert the list to a red-black tree break; } if (e.hash == hash && //If the elements in the list are exactly the same as those newly added during traversal, jump out of the loop ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; //The next node in P is assigned to p, that is, the next node in the list is assigned to P. //Continue to iterate through the elements in the list } } if (e != null) { //In this judgment, the code acts as: if the added element produces hash conflicts, then call //The put method returns the value of his previous element in the list V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) //If the judgment condition holds, replace old value //For new value, return old value; if it is not established, it will not be replaced, and then return old value. e.value = value; afterNodeAccess(e); return oldValue; } } ++modCount; //Record the number of modifications if (++size > threshold) //If the number of elements is greater than the critical value, the expansion is carried out. resize(); afterNodeInsertion(evict); return null; }
Not finished yet.