The Set interface, HashSet class, and LinkedHashSet class of the collection framework in Java (10,000 words super full detailed explanation)

1. Collective framework system

The Java collection framework provides a set of interfaces and classes with excellent performance and convenient use, which are located in the java.util package, so when using the collection framework, it is necessary to import the package.
The Java collection framework mainly includes two types of containers, one is a collection (Collection), which stores a collection of elements; the other is a map (Map), which stores key/value pair mappings.

  • The Collection system is as follows:
    Collection System Diagram
  • The Map system is as follows:

Explanation:
(1) The Collection interface has two important sub-interfaces List and Set, and their implementation subclasses are all single-column collections.
(2) The implementation subclass of the Map interface is a double-column collection, and the stored KV (key-value pair)

1. Overview of commonly used collection interfaces

As shown in the following table:

interface describe
Collection interface Collection is the most basic collection interface. A Collection represents a group of Objects (that is, the elements of Collection). Java does not provide classes directly inherited from Collection, but only subinterfaces (such as List and set) inherited from Collection interface. The Collection interface stores a set of non-unique and unordered objects (the objects in the Collection collection cannot be accessed through indexes).
List interface The List interface inherits from the Collection interface, but the List interface is an ordered collection. Using this interface can precisely control the insertion position of each element, and can pass the index (that is, the position of the element in the List, similar to the subscript of the array) To access the elements in the List collection, the index of the first element is 0. And the same elements are allowed in the List collection. It can be said that a collection of the List interface stores a set of non-unique, ordered (insertion order) objects.
Set interface The Set interface inherits from the Collection interface, and has exactly the same interface as the Collection, except that the methods are partially different. The same as the Collection interface, the Set interface stores a set of unique, unordered objects.
Map interface The Map interface is at the same level as the Collection interface (there is no inheritance relationship with each other). The Map graph stores a set of key-value objects and provides a mapping from key (key) to value (value).

Differences between Set and List interfaces:

(1) The Set interface collection stores unordered and non-repeated data. The List interface collection stores ordered and repeatable elements.

(2) The bottom layer of the Set collection uses a linked list data structure, which has low retrieval efficiency, high deletion and insertion efficiency, and insertion and deletion will not cause changes in element positions (the implementation subclasses include HashSet, TreeSet, etc.).

(3) List combined bottom layer is similar to array, but it can grow dynamically, automatically increasing the length of List according to the length of the actual stored data. It has high efficiency for retrieving elements, but low efficiency for insertion and deletion, which will cause changes in the position of other elements (the implementation subclasses include ArrayList, LinkedList, Vector, etc.).

2. Implementation subclasses of commonly used Collection collections

Java provides a set of standard collection classes that implement the Collection interface. Some of them are concrete classes, which can be used directly, while others are abstract classes, which provide a partial implementation of the interface.

As shown in the following table:

class name describe
ArrayList class This class implements the List interface, allows storing null (empty value) elements, and can store repeated elements. This class implements variable-sized arrays, providing better performance when accessing and traversing elements randomly. This class is asynchronous and should not be used in multi-threaded situations. When the ArrayList class is expanded, it will be expanded by 1.5 times the current capacity.
Vector class This class is very similar to the ArrayList class, but this class is synchronous and can be used in multi-threaded situations. This class allows setting the default growth length, and the default expansion method is twice the original size.
LinkedList class This class implements the List interface, allows storing null (empty value) elements, and can store repeated elements. It is mainly used to create a linked list data structure. This class has no synchronization method. If multiple threads access a LinkedList at the same time, you must implement the access by yourself Synchronization, the solution is to construct a synchronized LinkedList when creating the LinkedList class.
HashSet class This class implements the Set interface, does not allow to store duplicate elements, and does not guarantee the order of elements in the set, it allows to store null (empty value) elements, but only one at most can be stored.
TreeSet class This class implements the Set interface, does not allow to store duplicate elements, and does not guarantee the order of elements in the set, it allows to store null (empty value) elements, but only one at most can be stored. This class can implement functions such as sorting.

3. Implementation subclasses of commonly used Map graphs

As shown in the following table:

class name describe
HashMap class The HashMap class is a hash table that stores key-value pairs (key-value) mappings. This class implements the Map interface, stores y elements according to the HashCode value of the key, and has a fast access speed, but allows at most one record's key to be null (empty value), and it does not support thread synchronization.
TreeMap class The TreeMap class inherits AbstractMap, implements most of the Map interface, and uses a tree.
Hashtable 类 Hashtable inherits from Dictionary (dictionary) class and is used to store key-value pairs.
Properties class Properties inherits from Hashtable and represents a persistent property set. Each key and its corresponding value in the property list are a string.

Hereby note: Due to the various contents of the collection framework, this article only introduces the Set interface and its important implementation subclasses under the Collection collection, and the rest of the knowledge of the collection framework will be shared in the next blog post.

2. Collection interface

1. Common methods of Collection interface

  • Note: All subclass collections that implement the Collection interface can use the methods in the Collection interface. The following uses the ArrayList subclass that implements the List interface as an example, but any subclass that implements the Set interface can use the following methods.
  • Code:
import java.util.ArrayList;
import java.util.List;

public class CollectionMethod {
    
    
    public static void main(String[] args) {
    
    
		// 以实现了Collection 接口 的子类 ArrayList 来举例;
		
        ArrayList list = new ArrayList();

//      1.  add:添加单个元素
        list.add("jack");
        list.add(10);// 底层自动装箱:list.add(new Integer(10))
        list.add(true);// 同上
        System.out.println("list=" + list);// [jack, 10, true]

//      2.  addAll:添加多个元素
        ArrayList list2 = new ArrayList();// 创建一个新的集合
        list2.add("红楼梦");
        list2.add("三国演义");
        list.addAll(list2);
        System.out.println("list=" + list);// [jack, 10, true, 红楼梦, 三国演义]
      
//      3.  remove:删除指定元素,如果不指定则默认删除第一个元素
        list.remove(true);// 指定删除某个元素
        System.out.println("list=" + list);// [jack, 10, 红楼梦, 三国演义]

//      4.  removeAll:删除多个元素
        list.add("聊斋");
        list.removeAll(list2);
        System.out.println("list=" + list);// [jack, 10, 聊斋]
      
//      5.  contains:查找元素是否存在,返回 boolean 值
        System.out.println(list.contains("jack"));// T

//      6.  containsAll:查找多个元素是否都存在,返回 boolean 值
        System.out.println(list.containsAll(list2));// T
      
//      7.  size:获取元素个数
        System.out.println(list.size());// 3

//      8.  isEmpty:判断是否为空
        System.out.println(list.isEmpty());// F

//      9.  clear:清空
        list.clear();
        System.out.println("list=" + list);// []
    }
}

2. Iterators

  • Iterator (iterator) is not a collection, it is an interface used to access the Collection collection, mainly used to traverse the elements in the Collection collection, all subclass collections that implement the Collection interface can use iterators.

  • How iterators work:

  • The iterator is equivalent to a cursor, initially pointing to the previous position of the first element in the collection;
  • First use the hasNext() method to determine whether there is an element at the next position of the iterator,
  • If there is an element at the next position, use the next() method to return the next element, and move the position of the iterator back one bit.
  • If not, the iterator is directly exited without calling the next() method.

insert image description here

Common methods of iterators:
(1) Calling coll.next() will return the next element of the iterator and update the state of the iterator (the iterator moves down).
(2) Call list.hasNext() to determine whether there is another element in the collection.
(3) Call list.remove() to delete the elements returned by the iterator.

  • Note: Before using the next() method, you must use the hasNext() method to determine whether there is another element in the collection, otherwise an exception may occur.

  • Code demo:

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

public class CollectionIterator {
    
    
    public static void main(String[] args) {
    
    

        List list = new ArrayList();// 创建一个新的集合,该集合实现了 Collection 接口

        list.add(1);
        list.add(2);
        list.add(3);

        // 1. 先得到 list集合 对应的 迭代器
        // 使用集合的 iterator() 方法来获得该集合的迭代器;
        // 所有实现了 Collection 接口的子类都拥有 iterator() 方法
        Iterator iterator = list.iterator();

        // 2. 使用 while 循环 + 迭代器 遍历集合

        while (iterator.hasNext()) {
    
     // 首先判断集合中(下一个位置)是否还有元素

            // 若有,则获取下一个位置的元素,迭代器位置向下移一位
            Object obj = iterator.next();
            // 输出该元素
            System.out.println("obj=" + obj);

        }

        // 3. 注意:当退出 while 循环后 , 这时 iterator 迭代器,指向的是集合中的最后一个元素;
        //  若再次 使用 iterator.next();  会产生 NoSuchElementException 异常,因为集合中下一个位置没有元素存在了; 
        
        // 4. 如果希望再次遍历集合,需要重置 迭代器的状态;
        
        iterator = list.iterator();// 重置迭代器
        System.out.println("===第二次遍历===");
        while (iterator.hasNext()) {
    
    
            Object obj = iterator.next();
            System.out.println("obj=" + obj);
        }
    }
}

3. Collection traversal

  • Note: All subclass collections that implement the Collection interface can use the following traversal methods.

(1) Use iterators to traverse (iterators can be used in all collections to traverse).
(2) Use ordinary for loops to traverse (ordinary for loops obtain elements through the index of elements, which cannot be used to obtain elements in unordered collections. For example, subclass collections that implement the Set interface).
(3) Use the enhanced for loop to traverse (the bottom layer of the enhanced for loop is actually an iterator, so this method can be used in all collections).

  • Code:
import java.util.*;

public class ListFor {
    
    
    public static void main(String[] args) {
    
    

        ArrayList list = new ArrayList();// 创建一个有序的集合

        list.add("jack");
        list.add("tom");
        list.add("鱼香肉丝");
        list.add("北京烤鸭子");

        //遍历方式:
        //1. 迭代器
        Iterator iterator = list.iterator();
        while (iterator.hasNext()) {
    
    
            Object obj =  iterator.next();
            System.out.println(obj);

        }

        System.out.println("=====增强for=====");
        //2. 增强 for 循环
        for (Object o : list) {
    
    
            System.out.println("o=" + o);
        }

        System.out.println("=====普通for====");
        //3. 普通 for 循环
        for (int i = 0; i < list.size(); i++) {
    
    
            System.out.println("对象=" + list.get(i));
        }
    }
}

3. Set interface

  • The Set interface is a sub-interface of Collection, and the Set collection stores a set of unique, unordered elements (unordered means that the order of adding and removing elements of the Set interface is inconsistent, and the elements have no index).

  • The Set interface is a subinterface of Collection, so its common methods are the same as Collection interface.

  • Code:

public class SetMethod {
    
    
    public static void main(String[] args) {
    
    

        // 1. 以Set 接口的实现类 HashSet 来讲解Set 接口的方法
        // 2. set 接口的实现类的对象(Set接口对象), 不能存放重复的元素, 可以添加一个null
        // 3. set 接口对象存放元素是唯一和无序的(即添加的顺序和取出的顺序不一致)
        // 4. 注意:取出的顺序的顺序虽然不是添加的顺序,但是只要取出一次,它之后取出的顺序就一样了。
        Set set = new HashSet();
        set.add("john");
        set.add("lucy");
        set.add("john");// 元素重复,存入失败
        set.add("jack");
        set.add("hsp");
        set.add(null);// 可以添加 null
        set.add(null);// 再次添加null 会失败
        System.out.println(set);// 添加元素的顺序和打印出来的顺序不同
        System.out.println(set);// 但多次打印出来的顺序相同。

    }
}

The traversal method of the Set interface

  • Like the traversal method of the Collection interface, the specific traversal method of the Set interface can be used:

(1) use iterator to traverse
(2) use enhanced for loop
Note that elements cannot be retrieved using a normal for loop (because the objects in the Set cannot be retrieved by indexing).

  • Code:
public class SetMethod {
    
    
    public static void main(String[] args) {
    
    

        Set set = new HashSet();
        set.add("john");
        set.add("lucy");
        set.add("jack");
        set.add("hsp");
        set.add(null);
        
        // 遍历
        // 方式1: 使用迭代器
        
        Iterator iterator = set.iterator();
        while (iterator.hasNext()) {
    
    
            Object obj =  iterator.next();
            System.out.println("obj=" + obj);

        }

        // 方式2: 增强for
        System.out.println("=====增强for====");

        for (Object o : set) {
    
    
            System.out.println("o=" + o);
        }

        // set 接口集合,不能通过索引来获取元素,因此不能使用普通 for 循环来获取元素。 
        // 下面代码会报错!!
        for(int i = 0; i < set.size(); i++) {
    
    
            System.out.println(set.get(i));// 注意,根本没有get 方法
        }
    }
}

4. HashSet class (hash table)

1. Basic concept of HashSet class

  • The HashSet class implements the Set interface, does not allow to store duplicate elements, and does not guarantee the order of elements in the collection, it allows to store null (empty value) elements, but only one at most can be stored.

  • HashSet class is not thread safe, if multiple threads try to modify HashSet at the same time, the end result is undefined. Concurrent access to the HashSet must be explicitly synchronized when accessed by multiple threads.

  • Looking at the source code of the constructor of the HashSet class, we can find that the bottom layer of the HashSet class is actually implemented based on the HashMap class.

  • The HashSet class implements the Set interface, so the common methods in the HashSet class are consistent with those in the Collection interface, and the traversal method of the HashSet collection is consistent with that in the Set interface.

  • Code demo:


public class HashSet_ {
    
    
    public static void main(String[] args) {
    
    
        /*
        1. HashSet 类构造器的源码:
        
            public HashSet() {
                map = new HashMap<>();// HashSet 类基于 HashMap 类来创建
            }

        2. HashSet 可以存放null ,但是只能有一个null,即元素不能重复
         */
        
        Set hashSet = new HashSet();
        hashSet.add(null);
        hashSet.add(null);// 添加失败
        System.out.println("hashSet=" + hashSet);

    }
}

2. The underlying mechanism of the HashSet class (heavy and difficult!!)

The bottom layer of the HashSet class is the HashMap class, and the added elements are unordered and unique; the bottom layer of the HashSet class maintains a data structure of an array + one-way linked list (adjacency list).

  • Briefly explain the data structure of array + one-way linked list: there is an array, and each element in the array points to a one-way linked list. As shown below:
    insert image description here

  • Here we only analyze the underlying implementation mechanism of adding elements to the HashSet collection and the underlying expansion mechanism of the HashSet collection. Explain the conclusion first, and then analyze it in detail.

  • conclusion as below:

1. The underlying implementation mechanism of HashSet class object calling add() method to add elements
(1) When adding a new element, first according to the element'shashcode valueGet a corresponding hash value, and then set thehash valueinto a correspondingindex value.
(2) Secondly, search in the receiving table of the HashSet class object, first retrieve the array position corresponding to the index value, and check whether the element has been stored in this position.
(3) If there is no element stored in the position of the array, store the new element directly in the position of the array.

(4) If there are already elements stored in this position of the array (collectively referred to as old elements below), you need to call the equals() method to determine whether the new and old elements are the same.

(4.1) If the new element is the same as the old element, the new element will not be stored and the add() method will exit.

(4.2) If the new element is different from the old element, enter the one-way linked list pointed to by the position of the array, and judge again whether the linked list is an ordinary single-item linked list or an upgraded red-black tree.
(4.2.1) If it is already a red-black tree, use the method in the red-black tree for comparison. Since the red-black tree is too complicated, its bottom layer will not be analyzed here.
(4.2.2) If it is an ordinary one-way linked list, then traverse each node of the linked list in turn; if the traversal of the linked list ends, and it is found that the old elements and new elements stored in all nodes are different, the new element is added to the tail of the linked list (Store the new element into the HashSet collection), check whether the linked list is treed, and then exit the add method; if during the traversal process, it is found that there is an old element that is the same as the new element, the new element will not be stored, and the add method will exit.

  • Source demo:

public class HashSetSource {
    
    
    public static void main(String[] args) {
    
    

        HashSet hashSet = new HashSet();
        hashSet.add("java");// 到此位置,第1 次add分析完毕,
        hashSet.add("php");// 到此位置,第2 次add分析完毕,前两个元素添加在数组的不同位置。
        hashSet.add("java");// 在此位置,添加了已存在的元素,它和之前的元素重复了,所以它们 hash 值相同
        System.out.println("set=" + hashSet);
      HashSet 的源码解读:

1. 执行 HashSet() 方法:

    public HashSet() {
    
    
        map = new HashMap<>();// 底层是 HashMap 构造器;
    }
    
2. 执行 add() 方法:

   public boolean add(E e) {
    
    // e = "java"
        return map.put(e, PRESENT)==null;// (static) PRESENT = new Object(); 这个值始终是固定的值,没意义,作用是占位
   // 注意:底层 HashMap 的 put()方法需要传入键、值对(两个值),但实际 HashSet 是单列集合,不需要值,所以便将值这个位置的参数设置为静态变量,相当于没有作用。
   }
   
3.执行 put() , 该方法首先会执行 hash(key) 得到key 对应的 hash值,使用 算法 h = key.hashCode()) ^ (h >>> 16)
 
    public V put(K key, V value) {
    
     // key == "java", value == PRESENT 静态变量是共享的,在 HashSet 中没有作用。
        return putVal(hash(key), key, value, false, true);// hash值不等于 hashcode(),而是做了避免碰撞的处理
    }
    
 4.执行 putVal()方法:
 
 	final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
           boolean evict) {
    
    
        Node<K,V>[] tab; Node<K,V> p; int n, i; // 定义了辅助变量

        // table 就是 HashMap 的一个属性,类型是 Node[]数组
        // if 语句表示如果当前 table 是 null, 或者 大小=0 ;就调用 resize(),进行第一次扩容,到 16个空间.(该方法里面有个缓存算法),然后回到 putVal 方法;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
		// 开始添加元素

        // 若添加的索引位置未存放元素,看这个分支
        if ((p = tab[i = (n - 1) & hash]) == null) // i 是根据hash 值算出的索引
        /*
         (1) 根据 key与得到的 hash, 去计算该 key 应该存放到 table表的哪个索引位置,并把这个位置的先前存在的对象,赋给 p
         (2) 再判断 p (位置的对象)是否为null
             	(2.1) 如果p 为 null, 表示该位置还没有存放元素, 就创建一个Node (key="java", value = PRESENT)
             	(2.2) 然后放在该索引位置 tab[i] = newNode(hash, key, value, null)
         */    	
            tab[i] = newNode(hash, key, value, null); // 最后一个实参 null 就是 next值


        // 添加索引位置存在元素 ,看这个分支
        else {
    
    
            // 一个开发技巧提示: 在需要局部变量(辅助变量)时候,再创建
            Node<K,V> e; K k; // 辅助变量:辅助结点 e

            // 第一种情况,索引位置已存在的元素与新元素 “相同”
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                // 如果当前索引位置对应的链表的第一个元素和准备添加的key的hash值一样,并且满足 下面两个条件之一:
                    // (1) 准备加入的key 和 p 指向的Node 结点的 已存在key 是同一个对象
                    // (2) p 指向的Node 结点的 已存在key 的 equals() 和准备加入的 key比较后相同
                // 否则不能进入
                e = p; // 辅助结点 e 指向已存在的对象;

            // 第二种情况,索引位置已存在的元素与新元素 “不相同”,但 索引指向了一个红黑树
            else if (p instanceof TreeNode)
                 判断 p 是不是一颗红黑树,如果是一颗红黑树,就调用 putTreeVal , 来进行添加,
                 否则不能进入
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);

            // 第三种情况,索引位置已存在的元素与新元素 “不相同”,但 索引指向一个链表
            else {
    
    
                  // 如果table 对应索引位置,已经是一个链表, 就使用for 循环比较;
                 /*  (1) 依次和该链表的每一个元素比较后,都不相同, 则加入到该链表的最后;
                      注意在把元素添加到链表后,立即判断 该链表是否已经达到8个结点
                      , 就调用 treeifyBin() 对当前这个链表进行树化(转成红黑树);
                      注意,在转成红黑树时,要进行判断, 判断条件:
                  */    
                      if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY(64))
                              resize();
                    //  如果上面条件成立,先 进行table扩容.
                    //  只有上面条件不成立时,才进行转成红黑树
                    
                  // (2) 依次和该链表的每一个元素比较过程中,如果有相同情况,就直接break
                for (int binCount = 0; ; ++binCount) {
    
     // 死循环
                
               //     (1)依次和该链表的每一个元素比较后,都不相同, 则加入到该链表的最后
                    if ((e = p.next) == null) {
    
    
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD(8) - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                //    (2)依次和该链表的每一个元素比较过程中,如果有相同情况,就直接break
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;

                    p = e;// 将 p 指针往下移动,可以实现依次比较链表中的每个元素
                }
            }

            //最后判断 e 是否指向空值,如果指向空值,说明该链表中没有元素与新元素相同,跳过下面代码;
            //否则,说明 已存在重复元素,进入下面代码,不返回空值

            if (e != null) {
    
     // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue; // 退出putVal 方法,不返回空值
            }
        }
        ++modCount;
        
        // size: 每加入一个结点Node(k,v,h,next), size都会增加一次,不管是在数组或是索引位置。
        if (++size > threshold) // threshold 属性在resize() 中是一个缓冲值,当超过该值时就需要重新调用 resize 方法扩容
            resize();// 再次扩容
        afterNodeInsertion(evict);// 对于hashmap 来说,该方法是个空方法,是它留给它的子类去实现的
        return null;// 返回 null给 putVal方法,再返回null 给put 方法,代表成功了;如果不为null ,则失败了
	}
         

2. The underlying expansion of the HashSet collection and the red-black tree-based mechanism of the linked list
(1) The bottom layer of HashSet is HashMap. When using the add method to add elements, the resize() method will be called to expand the array.
(2) When adding elements to the HashSet collection for the first time, the resize() method will expand the size of the array (size variable) to 16; an array threshold threshold is set in the method, which is 0.75 times the size of the array, When the elements stored in the array reach the critical value, the resize() method will be called again to expand the array. The default is to expand to twice the size of the previous array, and then update the critical value threshold.
(3) When the number of linked list elements in an array position in the HashSet collection reaches a fixed value (8 by default), the collection will red-black tree the ordinary one-way linked list, but the premise is that the array The size of the array has reached 64; otherwise, the array needs to be expanded first (until the array size reaches 64), and then treed.

  • Code example:

public class HashSetIncrement {
    
    
    public static void main(String[] args) {
    
    
        /*
        HashSet 底层是HashMap, 第一次添加时,table 数组扩容到 16,
        临界值(threshold)是 16*加载因子(loadFactor)是0.75 = 12
        如果table 数组使用到了临界值 12,就会扩容到 16 * 2 = 32,
        新的临界值就是 32*0.75 = 24, 依次类推
         */
        HashSet hashSet = new HashSet();

        // 往集合中添加元素,下列的每个元素都是添加到集合的数组中,不会添加到链表中
        // 所以集合将会一直进行数组的扩容。
        for(int i = 1; i <= 100; i++) {
    
    
            hashSet.add(i); // 1,2,3,4,5...100
        }

        /*
        在Java8中, 如果一条链表的元素个数 >= TREEIFY_THRESHOLD(默认是 8 ),
        并且table的大小 >= MIN_TREEIFY_CAPACITY(默认64),就会进行树化(红黑树),
        否则仍然采用数组扩容机制,意思是说到第11个元素时就会扩容到64了
         */
        hashSet = new HashSet(); // 创建一个新的空集合

        // 往集合中添加元素,但与上面不同,这次添加的元素会添加到数组的同一个索引位置,
        // 因此数组的大小不会改变,依旧是默认的16。
        // 由于每个元素都不同,所以这些元素会加入到数组的单向链表中,
        // 当链表中的元素增加到8个时,集合就要将普通链表进行树化;
        // 但此时数组的大小为16,不满足树化要求的数组大小为64,所以要先进行数据的扩容;
        // 则新加入的第9、10个元素依然是添加到单向链表的后面,此时数组大小扩容到64;
        // 在添加第11 个元素时,数组大小和链表长度都满足了树化的条件,因此集合将链表进行树化。
        // 注意,本例中的每个元素都是不同的,但他们的hash 值相同,因此加入的数组的索引位置相同。
        for(int i = 1; i <= 12; i++) {
    
    
            hashSet.add(new A(i));
        }


        /*
            当我们向hashset增加一个元素,-> Node -> 加入table , 就算是增加了一个size
            在 table中 size > threshold ,就会扩容
         */

        hashSet = new HashSet(); // 再次创建一个新的空集合

        // 在 集合的某一条链表上添加了 7个 A对象
        for(int i = 1; i <= 7; i++) {
    
    
            hashSet.add(new A(i));
        }
        
        // 在另一条链表上添加到第4 个 B对象的时候,size = 12,到达临界值,数组会进行 resize()扩容
        // 但是由于为满足某条链表的元素个数 = 8,所以不会进行树化。 
        for(int i = 1; i <= 7; i++) {
    
    
            hashSet.add(new B(i));
        }
    }
}

class B {
    
    
    private int n;

    public B(int n) {
    
    
        this.n = n;
    }
    @Override
    public int hashCode() {
    
    
        return 200;
    }
}

class A {
    
    
    private int n;

    public A(int n) {
    
    
        this.n = n;
    }
    @Override
    public int hashCode() {
    
    
        return 100;
    }
}
  • The above explanation process requires friends to debug by themselves in order to truly understand.

Five, LinkedHashSet class (chained hash table)

1. The basic concept of the LinkedHashSet class

(1) The LinkedHashSet class is a subclass of the HashSet class, and all common methods and traversal methods in the HashSet class can be used.
(2) The bottom layer of the LinkedHashSet class is a LinkedHashMap, which maintains aArray + doubly linked listdata structure.
(3)The LinkedHashSet class determines the storage position of the element in the array according to the hashCode value of the element (same as HashSet), and uses the doubly linked list to maintain the order of the elements, which makes the elements appear to be saved in the order of insertion (that is, the order in which elements are added and The output order is the same), that is, the HashSet is extended.
(4) The LinkedHashSet class does not allow adding duplicate elements, allows null values, and cannot use indexes to obtain elements.

2. The underlying mechanism of the LinkedHashSet class

  • The schematic diagram is as follows:

insert image description here

  • Explanation: The underlying mechanism of the LinkedHashSet collection is actually similar to that of the HashSet collection, except that the one-way linked list is replaced by a two-way linked list, so the addition and removal of elements seems to be orderly, but in essence, the storage of elements in the LinkedHashSet collection is still random. In order, the index cannot be used to obtain the elements in the collection.

Six, TreeSet class

  • Source code analysis

public class TreeSet_ {
    
    
    public static void main(String[] args) {
    
    
    
        // 1. 当我们使用无参构造器,创建 TreeSet 时,仍然是无序的
        // 2. 老师希望添加的元素,按照字符串大小来排序
        // 3. 使用TreeSet 提供的一个构造器,可以传入一个比较器(匿名内部类),并指定排序规则

//      TreeSet treeSet = new TreeSet();// 普通构造器

        //  带参构造器
        TreeSet treeSet = new TreeSet(new Comparator() {
    
    
            @Override
            public int compare(Object o1, Object o2) {
    
    

             // return ((String) o2).compareTo((String) o1); 按照字符串大小排序
              
                return ((String) o1).length() - ((String) o2).length();// 按照长度大小排序
            }
        });

        // 添加数据
        treeSet.add("jack");
        treeSet.add("tom");// 3
        treeSet.add("sp");
        treeSet.add("a");
        treeSet.add("abc");// abc 长度和 tom 相同,加入失败


        System.out.println("treeSet=" + treeSet);
 
源码分析:

1. 构造器把传入的比较器对象,赋给了 TreeSet的底层的 TreeMap的属性this.comparator

    public TreeMap(Comparator<? super K> comparator) {
    
    
        this.comparator = comparator;
    }

2. 在 调用 treeSet.add("tom"), 在底层会执行到

    if (cpr != null) {
    
    //cpr 就是我们的匿名内部类(对象)
        do {
    
    
            parent = t;
            //动态绑定到我们的匿名内部类(对象)compare
            cmp = cpr.compare(key, t.key);
            if (cmp < 0)
                t = t.left;
            else if (cmp > 0)
                t = t.right;
            else //如果相等,即返回0,这个Key就没有加入
                return t.setValue(value);
        } while (t != null);
    }   

    }
}

Summarize

  • This article is the learning notes compiled and summarized by Xiaobai blogger when he was studying the Java online class of teacher Han Shunping at station B. Here, I would like to thank teacher Han Shunping for his online class. If you are interested, you can also go and have a look.
  • This article introduces the basic concepts of the collection framework in detail, and deeply explains the precautions and common methods of the Set interface, HashSet class, and LinkedHashSet class commonly used in the Collection collection, and introduces the use of iterators; it also analyzes the implementation of each subclass The source code of , and many, many examples, I hope you can gain something after reading it!
  • Finally, if there are any mistakes or omissions in this article, everyone is welcome to criticize and correct! work hard together! ! See you in the next blog post!

Guess you like

Origin blog.csdn.net/weixin_45395059/article/details/125826036