91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Java?HashSet怎么添加遍歷元素

發布時間:2022-07-13 14:17:48 來源:億速云 閱讀:190 作者:iii 欄目:開發技術

本篇內容主要講解“Java HashSet怎么添加遍歷元素”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“Java HashSet怎么添加遍歷元素”吧!

HashSet 類圖

Java?HashSet怎么添加遍歷元素

HashSet 簡單說明

1.HashSet 實現了 Set 接口

2.HashSet 底層實際上是由 HashMap 實現的

public HashSet() {
        map = new HashMap<>();
}

3.可以存放 null,但是只能有一個 null

4.HashSet 不保證元素是有序的(即不保證存放元素的順序和取出元素的順序一致),取決于 hash 后,再確定索引的結果

5.不能有重復的元素

HashSet 底層機制說明

HashSet 底層是 HashMapHashMap 底層是 數組 + 鏈表 + 紅黑樹

模擬數組+鏈表的結構

Java?HashSet怎么添加遍歷元素

/**
 * 模擬 HashSet 數組+鏈表的結構
 */
public class HashSetStructureMain {
    public static void main(String[] args) {
        // 模擬一個 HashSet(HashMap) 的底層結構
        // 1. 創建一個數組,數組的類型為 Node[]
        // 2. 有些地方直接把 Node[] 數組稱為 表
        Node[] table = new Node[16];
        System.out.println(table);
        // 3. 創建節點
        Node john = new Node("john", null);
        table[2] = jhon; // 把節點 john 放在數組索引為 2 的位置
        Node jack = new Node("jack", null);
        jhon.next = jack; // 將 jack 掛載到 jhon 的后面
        Node rose = new Node("rose", null);
        jack.next = rose; // 將 rose 掛載到 jack 的后面
        Node lucy = new Node("lucy", null);
        table[3] = lucy; // 將 lucy 放在數組索引為 3 的位置
        System.out.println(table);

    }
}

// 節點類 存儲數據,可以指向下一個節點,從而形成鏈表
class Node{
    Object item; // 存放數據
    Node next; // 指向下一個節點
    public Node(Object item, Node next){
        this.item = item;
        this.next = next;
    }
}

HashSet 添加元素底層機制

HashSet 添加元素的底層實現

1.HashSet 底層是 HashMap

2.當添加一個元素時,會先得到 待添加元素的 hash 值,然后將其轉換成一個 索引值

3.查詢存儲數據表(Node 數組) table,看當前 待添加元素 所對應的 索引值 的位置是否已經存放了 其它元素

4.如果當前 索引值 所對應的的位置不存在 其它元素,就將當前 待添加元素 放到這個 索引值 所對應的的位置

5.如果當前 索引值 所對應的位置存在 其它元素,就調用 待添加元素.equals(已存在元素) 比較,結果為 true,則放棄添加;結果為 false,則將 待添加元素 放到 已存在元素 的后面(已存在元素.next = 待添加元素)

HashSet 擴容機制

1.HashSet 的底層是 HashMap,第一次添加元素時,table 數組擴容到 cap = 16threshold(臨界值) = cap * loadFactor(加載因子 0.75) = 12

2.如果 table 數組使用到了臨界值 12,就會擴容到 cap * 2 = 32,新的臨界值就是 32 * 0.75 = 24,以此類推

3.在 Java8 中,如果一條鏈表上的元素個數 到達 TREEIFY_THRESHOLD(默認是 8),并且 table 的大小 >= MIN_TREEIFY_CAPACITY(默認是 64),就會進行 樹化(紅黑樹)

4.判斷是否擴容是根據 ++size > threshold,即是否擴容,是根據 HashMap 所存的元素個數(size)是否超過臨界值,而不是根據 table.length() 是否超過臨界值

HashSet 添加元素源碼

/**
 * HashSet 源碼分析
 */
public class HashSetSourceMain {
    public static void main(String[] args) {
        HashSet hashSet = new HashSet();
        hashSet.add("java");
        hashSet.add("php");
        hashSet.add("java");
        System.out.println("set = " + hashSet);

        // 源碼分析
        // 1. 執行 HashSet()
        /**
         * public HashSet() { // HashSet 底層是 HashMap
         *         map = new HashMap<>();
         *     }
         */

        // 2. 執行 add()
        /**
         * public boolean add(E e) { // e == "java"
         *         // HashSet 的 add() 方法其實是調用 HashMap 的 put()方法
         *         return map.put(e, PRESENT)==null; // (static) PRESENT = new Object(); 用于占位
         *     }
         */

        // 3. 執行 put()
        // hash(key) 得到 key(待存元素) 對應的hash值,并不等于 hashcode()
        // 算法是 h = key.hashCode()) ^ (h >>> 16
        /**
         * public V put(K key, V value) {
         *         return putVal(hash(key), key, value, false, true);
         *     }
         */

        // 4. 執行 putVal()
        // 定義的輔助變量:Node<K,V>[] tab; Node<K,V> p; int n, i;
        // table 是 HashMap 的一個屬性,初始化為 null;transient Node<K,V>[] table;
        // resize() 方法,為 table 數組指定容量
        // p = tab[i = (n - 1) & hash] 計算 key的hash值所對應的 table 中的索引位置,將索引位置對應的 Node 賦給 p
        /**
         * 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,或者 table.length == 0
         *         // 就是 table 第一次擴容,容量為 16
         *         if ((tab = table) == null || (n = tab.length) == 0)
         *             n = (tab = resize()).length;
         *         // 1. 根據 key,得到 hash 去計算key應該存放到 table 的哪個索引位置
         *         // 2. 并且把這個位置的索引值賦給 i;索引值對應的元素,賦給 p
         *         // 3. 判斷 p 是否為 null
         *         // 3.1 如果 p 為 null,表示還沒有存放過元素,就創建一個Node(key="java",value=PRESENT),并把這個元素放到 i 的索引位置
         *         // tab[i] = newNode(hash, key, value, null);
         *         if ((p = tab[i = (n - 1) & hash]) == null)
         *             tab[i] = newNode(hash, key, value, null);
         *         else {
         *             Node<K,V> e; K k; // 輔助變量
         *             // 如果當前索引位置對應的鏈表的第一個元素和待添加的元素的 hash值一樣
         *             // 并且滿足下面兩個條件之一:
         *             // 1. 待添加的 key 與 p 所指向的 Node 節點的key 是同一個對象
         *             // 2. 待添加的 key.equals(p 指向的 Node 節點的 key) == true
         *             // 就認為當前待添加的元素是重復元素,添加失敗
         *             if (p.hash == hash &&
         *                 ((k = p.key) == key || (key != null && key.equals(k))))
         *                 e = p;
         *             // 判斷 當前 p 是不是一顆紅黑樹
         *             // 如果是一顆紅黑樹,就調用 putTreeVal,來進行添加
         *             else if (p instanceof TreeNode)
         *                 e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
         *             else {
         *                  // 如果 當前索引位置已經形成一個 鏈表,就使用 for 循環比較
         *                  // 將待添加元素依次和鏈表上的每個元素進行比較
         *                  // 1. 比較過程中如果出現待添加元素和鏈表中的元素有相同的,比較結束,出現重復元素,添加失敗
         *                  // 2. 如果比較到鏈表最后一個元素,鏈表中都沒出現與待添加元素相同的,就把當前待添加元素放到該鏈表最后的位置
         *                  // 注意:在把待添加元素添加到鏈表后,立即判斷 該鏈表是否已經到達 8 個節點
         *                  // 如果到達,就調用 treeifyBin() 對當前這個鏈表進行數化(轉成紅黑樹)
         *                  // 注意:在轉成紅黑樹前,還要進行判斷
         *                  // if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
         *                  //        resize();
         *                  // 如果上面條件成立,先對 table 進行擴容
         *                  // 如果上面條件不成立,才轉成紅黑樹
         *                 for (int binCount = 0; ; ++binCount) {
         *                     if ((e = p.next) == null) {
         *                         p.next = newNode(hash, key, value, null);
         *                         if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
         *                             treeifyBin(tab, hash);
         *                         break;
         *                     }
         *                     if (e.hash == hash &&
         *                         ((k = e.key) == key || (key != null && key.equals(k))))
         *                         break;
         *                     p = e;
         *                 }
         *             }
         *             // e 不為 null ,說明添加失敗
         *             if (e != null) { // existing mapping for key
         *                 V oldValue = e.value;
         *                 if (!onlyIfAbsent || oldValue == null)
         *                     e.value = value;
         *                 afterNodeAccess(e);
         *                 return oldValue;
         *             }
         *         }
         *         ++modCount;
         *         // 擴容:說明判斷 table 是否擴容不是看 table 的length
         *         // 而是看 整個 HashMap 的 size(即已存元素個數)
         *         if (++size > threshold)
         *             resize();
         *         afterNodeInsertion(evict);
         *         return null;
         *     }
         */
    }
}

HashSet 遍歷元素底層機制

HashSet 遍歷元素底層機制

1.HashSet 的底層是 HashMapHashSet 的迭代器也是借由 HashMap 來實現的

2.HashSet.iterator() 實際上是去調用 HashMap 的 KeySet().iterator()

public Iterator<E> iterator() {
    return map.keySet().iterator();
}

3.KeySet() 方法返回一個 KeySet 對象,而 KeySet 是 HashMap 的一個內部類

public Set<K> keySet() {
    Set<K> ks = keySet;
    if (ks == null) {
        ks = new KeySet();
        keySet = ks;
    }
    return ks;
}

4.KeySet().iterator() 方法返回一個 KeyIterator 對象,KeyIterator 是 HashMap 的一個內部類

public final Iterator<K> iterator()     { return new KeyIterator(); }

5.KeyIterator 繼承了 HashIterator(HashMap的內部類) 類,并實現了 Iterator 接口,即 KeyIteratorHashIterator 才是真正實現 迭代器 的類

final class KeyIterator extends HashIterator
    implements Iterator<K> {
    public final K next() { return nextNode().key; }
}

6.當執行完 Iterator iterator = HashSet.iterator; 之后,此時的 iterator 對象中已經存儲了一個元素節點

  • 怎么做到的?

  • 回到第 4 步,KeySet().iterator() 方法返回一個 KeyIterator 對象

  • new KeyIterator() 調用 KeyIterator 的無參構造器

  • 在這之前,會先調用其父類 HashIterator 的無參構造器

  • 因此,分析 HashIterator 的無參構造器就知道發生了什么

/**
*         Node<K,V> next;        // next entry to return
*         Node<K,V> current;     // current entry
*         int expectedModCount;  // for fast-fail
*         int index;             // current slot
* HashIterator() {
*             expectedModCount = modCount;
*             Node<K,V>[] t = table;
*             current = next = null;
*             index = 0;
*             if (t != null && size > 0) { // advance to first entry
*                 do {} while (index < t.length && (next = t[index++]) == null);
*             }
*         }
*/
  • nextcurrentindex 都是 HashIterator 的屬性

  • Node<K,V>[] t = table; 先把 Node 數組 talbe 賦給 t

  • current = next = null; currentnext 都置為 null

  • index = 0; index 置為 0

  • do {} while (index < t.length && (next = t[index++]) == null); 這個 do-while 會在 table 中遍歷 Node 結點

  • 一旦 (next = t[index++]) == null 不成立 時,就說明找到了一個 table 中的 Node 結點

  • 將這個節點賦給 next,并退出當前 do-while 循環

  • 此時 Iterator iterator = HashSet.iterator; 就執行完了

  • 當前 iterator 的運行類型其實是 HashIterator,而 HashIterator 的 next 中存儲著從 table 中遍歷出來的一個 Node 結點

7.執行 iterator.hasNext

此時的 next 存儲著一個 Node,所以并不為 null,返回 true

public final boolean hasNext() {
    return next != null;
}

8.執行 iterator.next()

I.Node<K,V> e = next; 把當前存儲著 Node 結點的 next 賦值給了 e

II.(next = (current = e).next) == null 判斷當前結點的下一個結點是否為 null

  • (a). 如果當前結點的下一個結點為 null,就執行 do {} while (index < t.length && (next = t[index++]) == null);,在 table 數組中遍歷,尋找 table 數組中的下一個 Node 并賦值給 next

  • (b). 如果當前結點的下一個結點不為 null,就將當前結點的下一個結點賦值給 next,并且此刻不會去 table 數組中遍歷下一個 Node 結點

III.將找到的結點 e 返回

IV.之后每次執行 iterator.next() 都像 (a)(b) 那樣去判斷遍歷,直到遍歷完成

HashSet 遍歷元素源碼

/**
 * HashSet 源碼分析
 */
public class HashSetSourceMain {
    public static void main(String[] args) {
        HashSet hashSet = new HashSet();
        hashSet.add("java");
        hashSet.add("php");
        hashSet.add("java");
        System.out.println("set = " + hashSet);
        // HashSet 迭代器實現原理
        // HashSet 底層是 HashMap,HashMap 底層是 數組 + 鏈表 + 紅黑樹
        // HashSet 本身沒有實現迭代器,而是借由 HashMap 來實現的
        // 1. hashSet.iterator() 實際上是去調用 HashMap 的 keySet().iterator()
        /**
         * public Iterator<E> iterator() {
         *         return map.keySet().iterator();
         *     }
         */
        // 2. KeySet() 方法返回一個 KeySet 對象,而 KeySet 是 HashMap 的一個內部類
        /**
         * public Set<K> keySet() {
         *         Set<K> ks = keySet;
         *         if (ks == null) {
         *             ks = new KeySet();
         *             keySet = ks;
         *         }
         *         return ks;
         *     }
         */
        // 3. KeySet().iterator() 方法返回一個 KeyIterator 對象,KeyIterator 是 HashMap的一個內部類
        /**
         * public final Iterator<K> iterator()     { return new KeyIterator(); }
         */
        // 4. KeyIterator 繼承了 HashIterator(HashMap的內部類) 類,并實現了 Iterator 接口
        // 即 KeyIterator、HashIterator 才是真正實現 迭代器的類
        /**
         * final class KeyIterator extends HashIterator
         *         implements Iterator<K> {
         *         public final K next() { return nextNode().key; }
         *     }
         */
        // 5. 當執行完 Iterator iterator = hashSet.iterator(); 后
        // 此時的 iterator 對象中已經存儲了一個元素節點
        // 怎么做到的?
        // 回到第 3 步,KeySet().iterator() 方法返回一個 KeyIterator 對象
        // new KeyIterator() 調用 KeyIterator 的無參構造器
        // 在這之前,會先調用 KeyIterator 父類 HashIterator 的無參構造器
        // 因此分析 HashIterator 的無參構造器就知道發生了什么
        /**
         *         Node<K,V> next;        // next entry to return
         *         Node<K,V> current;     // current entry
         *         int expectedModCount;  // for fast-fail
         *         int index;             // current slot
         * HashIterator() {
         *             expectedModCount = modCount;
         *             Node<K,V>[] t = table;
         *             current = next = null;
         *             index = 0;
         *             if (t != null && size > 0) { // advance to first entry
         *                 do {} while (index < t.length && (next = t[index++]) == null);
         *             }
         *         }
         */
        // 5.0 next, current, index 都是 HashIterator 的屬性
        // 5.1 Node<K,V>[] t = table; 先把 Node 數組 table 賦給 t
        // 5.2 current = next = null; 把 current 和 next 都置為 null
        // 5.3 index = 0; index 置為 0
        // 5.4 do {} while (index < t.length && (next = t[index++]) == null);
        // 這個 do{} while 循環會在 table 中 遍歷 Node節點
        // 一旦 (next = t[index++]) == null 不成立時,就說明找到了一個 table 中的節點
        // 將這個節點賦給 next,并退出當前 do while循環
        // 此時 Iterator iterator = hashSet.iterator(); 就執行完了
        // 當前 iterator 的運行類型其實是 HashIterator,而 HashIterator 的 next 中存儲著從 table 中遍歷出來的一個 Node節點
        // 6. 執行 iterator.hasNext()
        /**
         * public final boolean hasNext() {
         *             return next != null;
         *         }
         */
        // 6.1 此時的 next 存儲著一個 Node,所以并不為 null,返回 true
        // 7. 執行 iterator.next(),其實是去執行 HashIterator 的 nextNode()
        /**
         * final Node<K,V> nextNode() {
         *             Node<K,V>[] t;
         *             Node<K,V> e = next;
         *             if (modCount != expectedModCount)
         *                 throw new ConcurrentModificationException();
         *             if (e == null)
         *                 throw new NoSuchElementException();
         *             if ((next = (current = e).next) == null && (t = table) != null) {
         *                 do {} while (index < t.length && (next = t[index++]) == null);
         *             }
         *             return e;
         *         }
         */
        // 7.1 Node<K,V> e = next; 把當前存儲著 Node 節點的 next 賦值給了 e
        // 7.2 (next = (current = e).next) == null
        // 判斷當前節點的下一個節點是否為 null
        // a. 如果當前節點的下一個節點為 null
        // 就執行 do {} while (index < t.length && (next = t[index++]) == null);
        // 再 table 數組中 遍歷,尋找 table 數組中的下一個 Node 并賦值給 next
        // b. 如果當前節點的下一個節點不為 null
        // 就將當前節點的下一個節點賦值給 next,并且此刻不會去 table 數組中遍歷下一個 Node 節點
        // 7.3 將找到的節點 e 返回
        // 7.4 之后每次執行 iterator.next(),都像 a、b 那樣去判斷遍歷,直到遍歷完成
        Iterator iterator = hashSet.iterator();
        while (iterator.hasNext()) {
            Object next =  iterator.next();
            System.out.println(next);
        }
    }
}

到此,相信大家對“Java HashSet怎么添加遍歷元素”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

新建县| 台南县| 巢湖市| 苍山县| 江阴市| 成武县| 张北县| 湟中县| 七台河市| 景洪市| 山东省| 平塘县| 富裕县| 永修县| 丹寨县| 永平县| 固安县| 曲水县| 页游| 河间市| 岳阳县| 年辖:市辖区| 明水县| 扎赉特旗| 景宁| 康平县| 故城县| 元谋县| 永登县| 三亚市| 政和县| 长白| 定陶县| 菏泽市| 晋城| 澄江县| 贵溪市| 吐鲁番市| 晋江市| 南郑县| 汨罗市|