java·數據結構·hashMap

来源:https://www.cnblogs.com/kunlingou/archive/2019/06/22/11067857.html
-Advertisement-
Play Games

特點 線程不安全 HashMap、和Hashtable、SynchronizedMap區別: HashMap 線程不安全,可以有null的key值或value值。 hashtable 線程安全,不能有null的key值或value值。 ConcurrentHashMap 線程安全,不能有null的k ...


特點

  • 線程不安全
  • HashMap、和Hashtable、SynchronizedMap區別:
    • HashMap 線程不安全,可以有null的key值或value值。
    • hashtable 線程安全,不能有null的key值或value值。
    • ConcurrentHashMap 線程安全,不能有null的key值或value值。刪除操作比較費時。
    • SynchronizedMap 線程安全,可以有null的key值或value值。
      • 可以通過Collections.synchronizedMap(new HashMap<String, Object>())方式創建
    • 性能:HashMap>ConcurrentHashMap>SynchronizedMap>Hashtable

構造方法

相關參數

  • initialCapacity:初始最大容量,預設1<<4(2^4),內部實際使用的變數是threshold(預設容量) ,實際最大容量並沒有存放。
  • loadFactor:載入因數(預設容量=初始最大容量*載入因數),預設0.75
  • threshold:預設容量,內部變數,根據initialCapacity生成。執行構造方法時,將輸入的initialCapacity轉為不小於當前數的最小的2^k的值,作為threshold。在第一次構建table時(第一次put(實際上時putVal方法),執行resize()方法),table的大小設置為threshold,然後讓threshold = threshold * loadFactor;後續每一次resize,都是table的大小 = table的大小 * 2;threshold = threshold * 2;
  • 預設關係:threshold = initialCapacity * loadFactor(達到最大容量時不滿足該等式)

平衡與折衷

  • 載入因數:hash表中元素的填滿程度,載入因數越大,空間利用率越高,衝突機會越高(查詢成本越高)

代碼解析

  • public HashMap(int initialCapacity, float loadFactor)
public HashMap(int initialCapacity, float loadFactor) {
        /**初始最大容量為非負整數*/
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        /**
        * static final int MAXIMUM_CAPACITY = 1 << 30;
        * 當 initialCapacity 大於最大容量(2^30,約10.74億)時,強制設置為容量為最大容量。
        */
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        /**
        * 載入因數為大於0的浮點數
        * public static boolean isNaN(float v) {
        *   return (v != v);
        * }
        * Float.isNaN(loadFactor):NaN(not-a-number),例如. float v = 0.0f/0.0f;
        */
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);
        /**賦值容量因數*/
        this.loadFactor = loadFactor;
        /**
        * 轉換輸入的初始最大容量為2^k,賦值給threshold作為實際最大容量
        * 這樣做的意義待分析
        */
        this.threshold = tableSizeFor(initialCapacity);
    }

/**
* 獲取不小於當前數的最小的2^k的值.
* 例如:31->32,65->128
*/
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;
}
  • public HashMap(int initialCapacity)
public HashMap(int initialCapacity) {
    this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
  • public HashMap()
/**
* 在resize()方法中設置threshold的值
* newCap = DEFAULT_INITIAL_CAPACITY;
* newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
*/
public HashMap() {
    this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
  • public HashMap(Map<? extends K, ? extends V> m)
    • Map<String,Object> map = new HashMap<>(); Map.putAll(mapEntries);
      =>(完全等價於)
      Map<String,Object> map = new HashMap<>(mapEntries);
      (LinkedHashMap 繼承於HashMap,該場景不一定完全等價,區別在於afterNodeInsertion方法,待梳理)
public HashMap(Map<? extends K, ? extends V> m) {
    this.loadFactor = DEFAULT_LOAD_FACTOR;
    putMapEntries(m, false);
}

public void putAll(Map<? extends K, ? extends V> m) {
    putMapEntries(m, true);
}

final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
    int s = m.size();
    /**
    * 當傳入的map映射中存有對象時,進行插入邏輯
    */
    if (s > 0) {
        /**
        * table == null ,這時threshold = 0,需要進行設置threshold的值,tableSizeFor方法作用可見上文。
        */
        if (table == null) { // pre-size
            float ft = ((float)s / loadFactor) + 1.0F;
            int t = ((ft < (float)MAXIMUM_CAPACITY) ?
                     (int)ft : MAXIMUM_CAPACITY);
            if (t > threshold)
                threshold = tableSizeFor(t);
        }
        /**
        * 如果傳入的映射中對象個數大於當前預設容量,容量擴大1倍
        * (put方法中已經有resize邏輯,該操作的意義待分析)
        */
        else if (s > threshold)
            resize();
        /**
        * 迴圈遍歷每一個對象進行插入操作,和put方法完全一樣
        */
        for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
            K key = e.getKey();
            V value = e.getValue();
            putVal(hash(key), key, value, false, evict);
        }
    }
}

put

相關參數

  • 暫未梳理

hashcode

  • hashcode(Object)只和物理地址有關,和對象的內容沒有關係。
class Student {
    String name;
    String[] likes;
    public Student(String name){
        this.name = name;
    }
    public Student(String name,String[] likes){
        this.name = name;
        this.likes = likes;
    }
}
System.out.println(new Student("a").equals(new Student("a")));//false
Student aa = new Student("a");
System.out.println(aa.hashCode());//1410986873
aa.name = "bcdefg";
System.out.println(aa.hashCode());//1410986873
aa.name = "a";
System.out.println(aa.hashCode());//1410986873

Student bb = new Student("a",new String[] {"愛好1","愛好2"});
System.out.println(bb.hashCode());//2110245805
bb.likes = new String[] {"愛好1","愛好4"};
System.out.println(bb.hashCode());//2110245805

HashMap<String, Object> hashMap2 = new HashMap<String, Object>(1 << 4);
for(int i=0;i<13;i++) {
    hashMap2.put(String.valueOf(i), 1);
}
System.out.println(hashMap2.hashCode());//5228

HashMap<String, Object> hashMap3 = new HashMap<String, Object>(1 << 4);
for(int i=0;i<13;i++) {
    hashMap3.put(String.valueOf(i), 1);
}
System.out.println(hashMap3.hashCode());//5228
System.out.println(((Object)hashMap2).equals(hashMap3));//true
  • hashMap.hashcode對hashcode方法進行了重寫,和key、value的hashcode有關係
public int hashCode() {
    int h = 0;
    Iterator<Entry<K,V>> i = entrySet().iterator();
    while (i.hasNext())
        h += i.next().hashCode();
    return h;
}

static class Node<K,V> implements Map.Entry<K,V> {
    public final int hashCode() {
        return Objects.hashCode(key) ^ Objects.hashCode(value);
    }
}
public final class Objects {
    public static int hashCode(Object o) {
        return o != null ? o.hashCode() : 0;
    }
}
public class Object {
    /**
    * 生成一個int類型的整型
    * 1.同一個對象(未發生變化)只能生成一個hashcode,如果equals(Object的equals方法),那麼hashcode一定相等。
    * 2.不同對象可能會生成一個hashcode
    * 3.Object的hashCode方法只和物理地址有關,和對象的內容沒有關係。
    */
    public native int hashCode();
}

代碼解析

  • static final int hash(Object key)
static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
  • final Node<K,V>[] resize()
/**
* 初始化或者給table容量加倍
*/
final Node<K,V>[] resize() {
    Node<K,V>[] oldTab = table;
    /**
    * 舊的最大容量,初始化時為0
    */
    int oldCap = (oldTab == null) ? 0 : oldTab.length;
    /**
    * 舊的預設容量,一定有值。
    */
    int oldThr = threshold;
    int newCap, newThr = 0;
    /**非初始化執行操作*/
    if (oldCap > 0) {
        if (oldCap >= MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return oldTab;
        }
        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                 oldCap >= DEFAULT_INITIAL_CAPACITY)
             //非處初始化操作,oldCap>=16時,thr已經很規範了,直接二倍即可。
            newThr = oldThr << 1; // double threshold
    }
    /**初始化執行的操作*/
    else if (oldThr > 0) // initial capacity was placed in threshold
        newCap = oldThr;
    /**理論上永遠也走不到該條件*/
    else {               // zero initial threshold signifies using defaults
        newCap = DEFAULT_INITIAL_CAPACITY;
        newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
    }
    //初始化操作時newThr == 0,非處初始化操作,但oldCap<16時,通過cap和factor生成thr。
    if (newThr == 0) {
        float ft = (float)newCap * loadFactor;
        newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                  (int)ft : Integer.MAX_VALUE);
    }
    /**
    * 代碼到這裡的時候,結構已經擴增完成了,得到了最終的threshold和table結構
    */
    threshold = newThr;
    @SuppressWarnings({"rawtypes","unchecked"})
        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
    table = newTab;
    /**
    * 將oldTab的值拷貝到newTab中
    */
    //table非null,性能優化
    if (oldTab != null) {
        for (int j = 0; j < oldCap; ++j) {
            /**
            * 為什麼在for迴圈內new對象,是否性能更高?
            */
            Node<K,V> e;
            //內容非null,性能優化
            if ((e = oldTab[j]) != null) {
                oldTab[j] = null;
                if (e.next == null)
                    //通過e的hash值和當前最大容量來確定一個唯一的hash值?簡單推測
                    newTab[e.hash & (newCap - 1)] = e;
                else if (e instanceof TreeNode)
                    //紅黑樹?待梳理
                    ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                else { // preserve orde
                    //對鏈表結構的處理
                    //低位組low
                    Node<K,V> loHead = null, loTail = null;
                    //高位組high
                    Node<K,V> hiHead = null, hiTail = null;
                    Node<K,V> next;
                    do {
                        next = e.next;
                        /**
                        * key為null,e.hash=0
                        * 初始化時oldcap = 0
                        */
                        if ((e.hash & oldCap) == 0) {
                            //第一次進入
                            if (loTail == null)
                                loHead = e;
                            else
                                loTail.next = e;
                            loTail = e;
                        }
                        else {
                            //第一次進入
                            if (hiTail == null)
                                hiHead = e;
                            else
                                hiTail.next = e;
                            hiTail = e;
                        }
                    } while ((e = next) != null);
                    //存在低位組的對象
                    if (loTail != null) {
                        //去掉無效的值,防止重覆計算高位對象
                        loTail.next = null;
                        newTab[j] = loHead;
                    }
                    //存在高位組的對象
                    if (hiTail != null) {
                        //去掉無效的值,防止重覆計算低位對象
                        hiTail.next = null;
                        newTab[j + oldCap] = hiHead;
                    }
                }
            }
        }
    }
    return newTab;
}
  • public V put(K key, V value)
public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}
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 ((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;
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;
        else if (p instanceof TreeNode)
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        else {
            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;
            }
        }
        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;
}

小結:

  • java位運算相關知識待歸納。 (位運算的目的是提高效率)
    • 1 << k(2^k)
    • ^是異或運算符,異或的規則是轉換成二進位比較,相同為0,不同為1.
    • int c=a ^ b ; a=c ^ b;b=c ^ a;
    • a&b 的操作的結果:a、b中對應位同時為1,則對應結果位也為1
  • double和float區別待歸納。
  • LinkedHashMap、HashMap、treemap、treenodes關係
  • 為什麼n初始化構造map時,轉換輸入的初始最大容量為2^k,賦值給threshold作為實際最大容量。

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 本文約3萬餘字,閱讀時間大概為1小時。主要包含:裸辭&辭職、西安&杭州、面試的技巧、螞蟻金服面試經歷、面試題分享等章節。距離來杭州已經過去3個月了,一直想把這幾個月的經歷寫下來,但是遲遲沒有動手,在整理了好久之後終於動手完成了本文,希望大家通過本文可以有所感悟。由於個人的工作經驗和視野有限,文章中的 ...
  • conda和pip簡介 conda conda是包及其依賴項和環境的管理工具。 適用語言:Python, R, Ruby, Lua, Scala, Java, JavaScript, C/C++, FORTRAN。 適用平臺:Windows, macOS, Linux 用途: 如果你需要的包要求不同 ...
  • 前幾天和朋友閑聊,說遇到了一個ConcurrentHashMap死迴圈問題,當時心裡想這不科學呀?ConcurrentHashMap怎麼還有死迴圈呢,畢竟它已經解決HashMap中rehash中死迴圈問題了,但是隨著深入的分析,發現事情並沒有之前想的那麼簡單~ (以下分析基於jdk版本:jdk1.8 ...
  • set 特點: 無序, 不允許重覆 沒有索引 Collections: Collections 與Collection 的區別: Collection是集合體系的最頂層,包含了集合體系的共性 Collecions是一個工具集,方法都是用於操作Collection map map是具有key和valu ...
  • JFrame的常用構造函數: JFrame() JFrame(String title) //視窗標題,會顯示在左上角窗體圖標的後面 JDialog的常用構造函數: JDialog() JDialog(JFrame/JDialog owner) //指定此對話框的所有者,當此對話框的所有者被關閉/最 ...
  • 信號量(semaphore),也和互斥鎖一樣提供了線程間或者進程間的同步功能。 信號量有三種: "Posix有名字的信號量" Posix基於記憶體的信號量 System V信號量 信號量比互斥鎖高級,互斥鎖只允許一個線程訪問臨界區,信號量可以多個,可以把信號量看作成互斥鎖的升級版,但是如果能用互斥鎖解 ...
  • ImageIcon是Icon介面的一個實現類。 ImageIcon類的構造函數: ImageIcon() ImageIcon(String filename) //本地圖片文件 ImageIcon(URL location) //網路圖片 ImageIcon(byte[] imageData) Im ...
  • [TOC] Jupyter Jupyter Notebook(此前被稱為 IPython notebook)是一個互動式筆記本。它的本質是一個 Web 應用程式,便於創建和共用文學化程式文檔,支持實時代碼,數學方程,可視化和markdown。用途包括:數據清理和轉換,數值模擬,統計建模,機器學習等等 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...