1. 程式人生 > >Java資料結構詳解(十二)- HashMap

Java資料結構詳解(十二)- HashMap

HashMap

基於雜湊表的 Map 介面的實現。此實現提供所有可選的對映操作,並允許使用 null 值和 null 鍵。(除了非同步和允許使用 null 之外,HashMap 類與 Hashtable 大致相同。)此類不保證對映的順序,特別是它不保證該順序恆久不變。

此實現假定雜湊函式將元素適當地分佈在各桶之間,可為基本操作(get 和 put)提供穩定的效能。迭代 collection 檢視所需的時間與 HashMap 例項的“容量”(桶的數量)及其大小(鍵-值對映關係數)成比例。所以,如果迭代效能很重要,則不要將初始容量設定得太高(或將載入因子設定得太低)。

HashMap 的例項有兩個引數影響其效能:初始容量 和載入因子。容量 是雜湊表中桶的數量,初始容量只是雜湊表在建立時的容量。載入因子 是雜湊表在其容量自動增加之前可以達到多滿的一種尺度。當雜湊表中的條目數超出了載入因子與當前容量的乘積時,則要對該雜湊表進行 rehash 操作(即重建內部資料結構),從而雜湊表將具有大約兩倍的桶數。

通常,預設載入因子 (.75) 在時間和空間成本上尋求一種折衷。載入因子過高雖然減少了空間開銷,但同時也增加了查詢成本(在大多數 HashMap 類的操作中,包括 get 和 put 操作,都反映了這一點)。在設定初始容量時應該考慮到對映中所需的條目數及其載入因子,以便最大限度地減少 rehash 操作次數。如果初始容量大於最大條目數除以載入因子,則不會發生 rehash 操作。

如果很多對映關係要儲存在 HashMap 例項中,則相對於按需執行自動的 rehash 操作以增大表的容量來說,使用足夠大的初始容量建立它將使得對映關係能更有效地儲存。

注意,此實現不是同步的。如果多個執行緒同時訪問一個雜湊對映,而其中至少一個執行緒從結構上修改了該對映,則它必須 保持外部同步。(結構上的修改是指新增或刪除一個或多個對映關係的任何操作;僅改變與例項已經包含的鍵關聯的值不是結構上的修改。)這一般通過對自然封裝該對映的物件進行同步操作來完成。如果不存在這樣的物件,則應該使用 Collections.synchronizedMap 方法來“包裝”該對映。最好在建立時完成這一操作,以防止對對映進行意外的非同步訪問,如下所示:

Map m = Collections.synchronizedMap(new HashMap(…));由所有此類的“collection 檢視方法”所返回的迭代器都是快速失敗 的:在迭代器建立之後,如果從結構上對對映進行修改,除非通過迭代器本身的 remove 方法,其他任何時間任何方式的修改,迭代器都將丟擲 ConcurrentModificationException。因此,面對併發的修改,迭代器很快就會完全失敗,而不冒在將來不確定的時間發生任意不確定行為的風險。

HashMap

//預設初始化容量 :16 必須是2的冪。
static final int DEFAULT_INITIAL_CAPACITY = 1
<< 4; //最大容量:1073741824 必須是2的冪 static final int MAXIMUM_CAPACITY = 1 << 30; //預設的載入因子:0.75 static final float DEFAULT_LOAD_FACTOR = 0.75f; //使用樹的閾值:8 .在鏈地址法的節點到達閾值 8 之後轉化為紅黑樹. static final int TREEIFY_THRESHOLD = 8; //解除安裝樹的閾值: 6 如果發現連結串列長度小於 6,則會由樹重新退化為連結串列. static final int UNTREEIFY_THRESHOLD = 6; //最小的hashmap容量:64 在轉變成樹之前,還會有一次判斷,只有鍵值對數量大於 64 才會發生轉換。這是為了避免在雜湊表建立初期,多個鍵值對恰好被放入了同一個連結串列中而導致不必要的轉化。 static final int MIN_TREEIFY_CAPACITY = 64;

HashMap.Node
HashMap的節點,和LinkedList的節點差不多,就是多了點東西.

    static class Node<K,V> implements Map.Entry<K,V> {
        //hash值
        final int hash;
        final K key;
        V value;

        //下一個Node的引用.
        Node<K,V> next;

        //構造方法
        Node(int hash, K key, V value, Node<K,V> next) {
            this.hash = hash;
            this.key = key;
            this.value = value;
            this.next = next;
        }
        //get方法
        public final K getKey()        { return key; }
        public final V getValue()      { return value; }
        //toString方法
        public final String toString() { return key + "=" + value; }
        //hashCode方法
        public final int hashCode() {
            return Objects.hashCode(key) ^ Objects.hashCode(value);
        }
        //set方法
        public final V setValue(V newValue) {
            V oldValue = value;
            value = newValue;
            return oldValue;
        }
        //equals方法
        public final boolean equals(Object o) {
            if (o == this)
                return true;
            if (o instanceof Map.Entry) {
                Map.Entry<?,?> e = (Map.Entry<?,?>)o;
                if (Objects.equals(key, e.getKey()) &&
                    Objects.equals(value, e.getValue()))
                    return true;
            }
            return false;
        }
    }
    /* ---------------- Static utilities -------------- */

靜態工具方法

    static final int hash(Object key) {
        int h;
        //如果key為null 返回0 ,否則返回key的hashCode與h>>>16  進行異或運算的結果
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
    //將給定的初始容量格式化成2的冪的方法,比如輸入4,得到4,輸入7得到8。
    static final int tableSizeFor(int cap) {
        int n = cap - 1;
        n |= n >>> 1;
        n |= n >>> 2;
        n |= n >>> 4;
        n |= n >>> 8;
        n |= n >>> 16;
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }
/* ---------------- Fields -------------- */

HashMap Fields

//陣列形式的節點 table
transient Node<K,V>[] table;

//鍵值對
transient Set<Map.Entry<K,V>> entrySet;

//鍵值對對映的數量
transient int size;

//改變的次數(HashMap不是執行緒同步的)
transient int modCount;

//調整大小的下一個大小值
int threshold;

//雜湊表的負載因子
final float loadFactor;

HashMap的構造方法

    //無參構造器,使用預設的負載因子
    public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }
    //給定初始容量引數的構造方法,將會呼叫下一個構造方法,引數為預設的負載因子
    public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }
    //引數為初始化容量,和負載因子
    public HashMap(int initialCapacity, float loadFactor) {

        //初始容量引數的一系列判斷.........start
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);
        //初始容量引數的一系列判斷.........end


        this.loadFactor = loadFactor;
        //將初始容量引數通過tableSizeFor格式化成2的冪.並返回給threshold 
        this.threshold = tableSizeFor(initialCapacity);
    }
    public HashMap(Map<? extends K, ? extends V> m) {
        //使用預設負載因子
        this.loadFactor = DEFAULT_LOAD_FACTOR;
        putMapEntries(m, false);
    }

HashMap -put

    //返回putVal方法
    public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

putVal();

    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {

        //節點tab 節點p  int n 和i ;
        Node<K,V>[] tab; Node<K,V> p; int n, i;

        //將欄位中的table賦值給tab並判斷tab是不是為null.
        if ((tab = table) == null || (n = tab.length) == 0)
            //建立一個新表
            n = (tab = resize()).length;

            //確定元素存放在哪個桶中,桶為空,(此時,這個結點是放在陣列中)
        if ((p = tab[i = (n - 1) & hash]) == null)
            //將新生成結點放入桶中
            tab[i] = newNode(hash, key, value, null);
        else {//桶不為空,已經有元素在這個桶中,這個時候要解決桶衝突問題.
            Node<K,V> e; K k;

            //將傳進來的引數與桶中第一個元素(陣列中的結點)的hash值比較,key比較.
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                // 將第一個元素賦值給e,用e來記錄
                e = p;
                //// hash值不相等,即key不相等;判斷p是否為紅黑樹結點
            else if (p instanceof TreeNode)
                //將p轉換為紅黑樹並將引數新增到樹節點中
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {//為連結串列.

                //for迴圈(一直迴圈)
                for (int binCount = 0; ; ++binCount) {

                    // 到達連結串列的尾部
                    if ((e = p.next) == null) {

                        //將引數新增到連結串列尾部
                        p.next = newNode(hash, key, value, null);

                        //如果連結串列長度大於閾值TREEIFY_THRESHOLD則轉換為紅黑樹.
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                            //跳出
                        break;
                    }

                    //如果hash相等,key相等 說明連結串列中已經有相同的元素.則跳出迴圈.
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                        //和p.next 結合控制連結串列的遍歷
                    p = e;
                }
            }
            // 表示在桶中找到key值、hash值與插入元素相等的結點
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                 // 返回舊值
                return oldValue;
            }
        }
        ++modCount;
        // 實際大小大於閾值則擴容
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

HashMap-get

    public V get(Object key) {
        Node<K,V> e;
        return (e = getNode(hash(key), key)) == null ? null : e.value;
    }
    final Node<K,V> getNode(int hash, Object key) {
        Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
        // table已經初始化,長度大於0,根據hash尋找table中的項也不為空
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (first = tab[(n - 1) & hash]) != null) {
            // 桶中第一項(陣列元素)相等
            if (first.hash == hash && // always check first node
                ((k = first.key) == key || (key != null && key.equals(k))))
                return first;
            if ((e = first.next) != null) {// 桶中不止一個結點

                // 判斷是不是為紅黑樹節點
                if (first instanceof TreeNode)
                //在紅黑樹中查詢
                    return ((TreeNode<K,V>)first).getTreeNode(hash, key);
                    // 否則,在連結串列中查詢
                do {
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
                } while ((e = e.next) != null);
            }
        }
        return null;
    }

相關推薦

Java資料結構- HashMap

HashMap 基於雜湊表的 Map 介面的實現。此實現提供所有可選的對映操作,並允許使用 null 值和 null 鍵。(除了非同步和允許使用 null 之外,HashMap 類與 Hashtable 大致相同。)此類不保證對映的順序,特別是它不保證該順序恆

Java資料結構-Collection介面

Java資料結構-collection介面 一,Collection介面結構圖 Collection介面詳解 collection是一個被高度抽象出來的介面、提供基本的操作資料的行為、屬性的定義. collection api介紹: p

【linux】Valgrind工具集:DHAT:動態堆分析器

一、概述 DHAT動態堆分析器。Massif(堆分析器)是在程式結束後輸出分析結果,而DHAT是實時輸出結果,所以叫做動態堆分析器。Massif只記錄堆記憶體的申請和釋放,DHAT還會分析堆空間的使用率、使用週期等資訊。 DHAT的功能:它首先記錄在堆上分配的塊,通過分析每次記憶體訪

Java入門提高篇】Day34 Java容器類WeakHashMap

public class WeakHashMapTest { public static void main(String[] args){ testWeakHashMap(); } private static void testWeakHashMap

資料結構與演算法並查集(Union Find)

本文主要包括以下內容: 並查集的概念 並查集的操作 並查集的實現和優化 Quick Find Quick Union 基於size的優化 基於rank的優化 路徑壓縮優化 並查集的時間複雜度 並查集的概念 在電腦科學中,並查集 是一種樹形的資料結

Pygame:Surface 物件

pygame.Surface Pygame 中用於表示影象的物件。 Surface((width, height), flags=0, depth=0, masks=None) -> Surface Surface((width, height), flags=0, Surfa

SpringBoot開發 -- SpringBoot中執行定時任務

最近在專案中一直使用定時任務完成一些業務邏輯,比如天氣介面的資料獲取,定時傳送簡訊,郵件。以及商城中每天使用者的限額,定時自動收貨等等。定時器在專案中是我們常常會使用到的一個手段,今天我們就來看下在SpringBoot中如何整合定時任務。 定時任務在Sprin

Java虛擬機器------雙親委派模型

  在上一篇部落格,我們介紹了類載入過程,包括5個階段,分別是“載入”,“驗證”,“準備”,“解析”,“初始化”,如下圖所示:        本篇部落格,我們來介紹Java虛

Redis------ 快取穿透、快取擊穿、快取雪崩

  本篇部落格我們來介紹Redis使用過程中需要注意的三種問題:快取穿透、快取擊穿、快取雪崩。 1、快取穿透 一、概念   快取穿透:快取和資料庫中都沒有的資料,可使用者還是源源不斷的發起請求,導致每次請求都會到資料庫,從而壓垮資料庫。   如下圖紅色的流程:         比如客戶查

hashmap資料結構HashMap、HashTable、ConcurrentHashMap 的區別

【hashmap 與 hashtable】   hashmap資料結構詳解(一)之基礎知識奠基 hashmap資料結構詳解(二)之走進JDK原始碼 hashmap資料結構詳解(三)之hashcode例項及大小是2的冪次方解釋 hashmap資料結構詳解(四)之has

Tire樹字典樹資料結構圖解及模板

先在這裡放模板,具體圖解回去再發 #include <map> #include <queue> #include <cstdlib> #include <cm

2019.9.15 初級資料結構專題待填坑——全篇序

雖然剛剛初三,卻有點要退役的感覺。 記得還在剛剛開始接觸演算法和資料結構時,全班30多人窩在當時只有一個空調的小機房裡,每人抱著一本《資訊學奧賽一本通》(就是常說的橙書),過了2個小時,全班沒有一個人能看懂簡簡單單一個廣搜。 於是我當時就立志,要寫能讓所有人都看懂的資料結構和演算法詳解。如果做不到,那就問

Java虛擬機器------類載入過程

  在上一篇文章中,我們詳細的介紹了Java類檔案結構,那麼這些Class檔案是如何被載入到記憶體,由虛擬機器來直接使用的呢?這就是本篇部落格將要介紹的——類載入過程。 1、類的生命週期   類從被載入到虛擬機器記憶體開始,到卸載出記憶體為止,其宣告週期流程如下:      上

JAVA基礎學習之路鏈表

args 是否為空 鏈表 == lin 一個 ava int 數據類型 定義鏈表的基本結構: class Link {//外部類 //內部類,只為鏈表類服務 private class Node {//定義節點類 private

Java Scanner 類附例子學習

在筆試程式設計過程中,關於資料的讀取如果迷迷糊糊,那後來的程式設計即使想法很對,實現很好,也是徒勞,於是在這裡認真總結了Java  Scanner 類的使用 通過 Scanner 類來獲取使用者的輸入,下面是建立 Scanner 物件的基本語法: Scanner s =

【linux】Valgrind工具集:Callgrind效能分析圖

一、概述 1、Callgrind Callgrind用於記錄程式中函式之間的呼叫歷史資訊,對程式效能分析。預設情況下,收集的資料包括執行的指令數,它們與原始碼行的關係,函式之間的呼叫者、被呼叫者關係以及此類呼叫的數量。可選項是,對快取記憶體模擬和分支預測(類似於Cachegrin

【linux】Valgrind工具集:Cachegrind快取和分支預測分析器

一、概述 Cachegrind,它模擬CPU中的一級快取I1,Dl和二級快取,能夠精確地指出程式中cache的丟失和命中。如果需要,它還能夠為我們提供cache丟失次數,記憶體引用次數,以及每行程式碼,每個函式,每個模組,整個程式產生的指令數。這對優化程式有很大的幫助。 Cach

【linux】Valgrind工具集:Massif堆分析器

一、概述 Massif是一個堆分析器。它統計程式使用的堆記憶體大小(由malloc等函式分配的記憶體)。預設情況下不統計程式所使用的所有記憶體,如果想統計所有記憶體,需要使用選項–pages-as-heap=yes。 堆分析可以幫助減少程式使用的記憶體。如果分配的記憶體還沒有釋放

Tomcat目錄結構非常詳細

Tomcat7 的目錄結構如圖: · 1、bin:該目錄下存放的是二進位制可執行檔案,如果是安裝版,那麼這個目錄下會有兩個exe檔案:tomcat6.exe、tomcat6w.exe,前者是在控制檯下啟動Tomcat,後者是彈出UGI視窗啟動Tomcat;如果是解壓版,那

Tkinter 元件:Text

Text(文字)元件用於顯示和處理多行文字。在 Tkinter 的所有元件中,Text 元件顯得異常強大和靈活,適用於多種任務。雖然該元件的主要目的是顯示多行文字,但它常常也被用於作為簡單的文字編輯器和網頁瀏覽器使用。 何時使用 Text 元件? Text 元件用於顯示文字文件,包含純文