1. 程式人生 > >HashMap原理。圖文並茂式解讀。這些注意點你一定還不瞭解

HashMap原理。圖文並茂式解讀。這些注意點你一定還不瞭解

目錄

  • 概述
  • 屬性詳解
    • table
    • entrySet
    • size
    • modCount
    • threshold、loadFactor
  • 原始碼知識點必備
    • getGenericInterfaces和getInterfaces區別
    • ParameterizedType
    • getRawType
    • getActualTypeArguments
    • getOwnerType
    • comparableClassFor
    • (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16)
  • hash表維護
  • HashMap 陣列長度是2^n ?
  • 神奇的hashmap遍歷
  • put 流程跟蹤
  • 寒暄一句
  • # 加入戰隊
    • 微信公眾號

概述

本篇文章我們來聊聊大家日常開發中常用的一個集合類 - HashMap。HashMap 最早出現在 JDK 1.2中,底層基於雜湊演算法實現。HashMap 允許 null 鍵和 null 值,在計算哈鍵的雜湊值時,null 鍵雜湊值為 0。HashMap 並不保證鍵值對的順序,這意味著在進行某些操作後,鍵值對的順序可能會發生變化。另外,需要注意的是,HashMap 是非執行緒安全類,在多執行緒環境下可能會存在問題。

屬性詳解

DEFAULT_INITIAL_CAPACITY 預設初始容量
MAXIMUM_CAPACITY 最大容量
DEFAULT_LOAD_FACTOR 預設負載因子
TREEIFY_THRESHOLD 一個桶的樹化閾值(超過此值會變成紅黑樹)
UNTREEIFY_THRESHOLD 一個樹的連結串列還原閾值(小於此值在resize的時候會變回連結串列)
MIN_TREEIFY_CAPACITY 雜湊表的最小樹形化容量(為了避免進行擴容、樹形化選擇的衝突,這個值不能小於 4 * TREEIFY_THRESHOLD)

table

HashMap中的陣列(hash表)。hash表的長度總是在2^n。至於原因嗎,後面專門會說的。數組裡儲存的是Node節點的資料

entrySet

Node<K,V> 節點構成的 set

size

當前map中儲存節點的資料

modCount

hashMap發生結構性變化的次數,節點轉紅黑樹、擴容等操作。

threshold、loadFactor

擴容闕值和裝載因子。

原始碼知識點必備

getGenericInterfaces和getInterfaces區別

getGenericInterfaces獲取直接介面
getInterfaces獲取所有介面

ParameterizedType

是Type的子介面,表示一個有引數的型別。就是我們俗稱的泛型。實現這個介面的類必須提供equals方法。

getRawType

返回最外層<>前面那個型別,即Map<K ,V>的Map。

getActualTypeArguments

獲取“泛型例項”中<>裡面的“泛型變數”(也叫型別引數)的值,這個值是一個型別。因為可能有多個“泛型變數”(如:Map<K,V>),所以返回的是一個Type[]。
注意:無論<>中有幾層<>巢狀,這個方法僅僅脫去最外層的<>,之後剩下的內容就作為這個方法的返回值,所以其返回值型別是不確定的。

getOwnerType

獲得這個型別的所有者的型別,主要對巢狀定義的內部類而言。列如對java.util.Map.Node<K,V> 呼叫getOwnerType方法返回的是interface java.util.Map介面

comparableClassFor

HashMap類中有一個comparableClassFor(Object x)方法,當x的型別為X,且X直接實現了Comparable介面(比較型別必須為X類本身)時,返回x的執行時型別;否則返回null。通過這個方法,我們可以搞清楚一些與型別、泛型相關的概念和方法

(key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16)

hashCode與自己的高16為進行異或 。 這樣更分散
ps:
& : 全部為1則為1,否則為0 偏0
| : 有一個為1則為1,否則為0 偏1
^ : 相同為0 不同為1 更加均衡。 均勻(分散)

hash表維護

在文章開頭我們就解釋了HashMap中table就是我們的hash表。直觀上我們可以理解成一個開闢空間的陣列。HashMap通過hash(key)這個方法獲取hash值。然後通過hash值確定key在hash表中的位置((n - 1) & hash)。

綜合上圖我們也會發現問題了。key的個數是無限的。但是我們的hash表是有限的。如何能保證hash(key)不會落在同一個位置呢。答案是不能。換句話說就是我們hash(key)無法保證。也就是hashMap會發生hash碰撞的。hash函式只能儘量避免hash碰撞。上面的(key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16)就是為了讓hash更加分散點。這一點上面也作出瞭解釋。

HashMap 陣列長度是2^n ?

上面解釋了hashmap中hash函式為什麼要^ 。 那麼深度原始碼的小夥伴可能會問,為什麼hashmap預設容量是16以及後期每次擴容的時候為什麼是翻倍擴容。簡而言之。為什麼hashMap陣列長度永遠是2的倍數呢。
上面我們知道如何通過hash確定在陣列中位置的。
(n - 1) & hash
關於這個n是陣列的長度,hash就是key值通過hash函式計算出來的hash值。
& 運算規則是: 全部為1則為1,否則為0
假設目前hashMap容量是16 , 我們來看看在擴容前後我們key的在是陣列中的索引。

經過圖片鮮明的對比我們發現,擴容前後是不會影響原來資料(高位為0)的索引位置的。這裡要注意的是並不是說所有資料不受影響,只要原來從右至左第五位為0的hash會受影響,其他不會。這樣大大減少了陣列位置調換的操作。效能上也大大的提高了。從這裡也可以看出hashmap容量越大,擴容是越複雜,因為容量越大,需要換位置的索引越多。
那麼如果我們擴容是不是選擇擴大2倍 , 我們看看會發生什麼樣情況。

上圖中是有16擴充套件成了24容量。這個時候我們會發現除了(從右至左)第五位以為第四位的資料也發生了變化。這樣造成的介面是第四位和第五位的資料都會變化。這樣增加了索引位置的數量。所以我們需要在每次擴容為原來的2倍。

神奇的hashmap遍歷

做Java的肯定會遇到的一種情況是,為什麼我的map遍歷的順序和我新增的順序不一致呢。有時候我們做列表展示的時候對順序是有要求的。但是hashmap偏偏和我們想的不一樣。今天華仔帶你看看為什麼會出現這種神奇的遍歷。

public final void forEach(Consumer<? super K> action) {
    Node<K,V>[] tab;
    if (action == null)
        throw new NullPointerException();
    if (size > 0 && (tab = table) != null) {
        int mc = modCount;
        for (int i = 0; i < tab.length; ++i) {
            for (Node<K,V> e = tab[i]; e != null; e = e.next)
                action.accept(e.key);
        }
        if (modCount != mc)
            throw new ConcurrentModificationException();
    }
}

從上面的程式碼我們可以看出來hashmap在遍歷時候,是先遍歷陣列然後取到陣列中連結串列(紅黑樹)按照順序獲取node節點的。也即是說我們先按陣列再按連結串列順序。而不是按照你新增先後的順序。而上面我們瞭解新增的node決定其位置的是key的hash值。所以這就解釋了為什麼hashmap遍歷的時候和我們新增不一致的了。

put 流程跟蹤


public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}

其他方法原理是相同的。值得注意的是remove後臨界情況會發生紅黑樹轉連結串列。所以轉紅黑樹的這個闕值的選取有時候會影響效能的高低。下面看看put的實際原始碼吧。拜讀下大佬的程式碼。

上面的程式碼可以看出來put實際呼叫的方法是putVal();
int hash : key對應的hash值
K key, : key
V value, : value
onlyIfAbsent : 如果存在則忽略,預設false表示新值會覆蓋舊值
boolean evict: 表示是否在構造table時呼叫


/**
* Implements Map.put and related methods
*
* @param hash hash for key
* @param key the key
* @param value the value to put
* @param onlyIfAbsent if true, don't change existing value
* @param evict if false, the table is in creation mode.
* @return previous value, or null if none
*/
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;
}

寒暄一句

個人幾天時間總結的,有網上前輩的總結,也有加入個人的想法。
再次申明:以上圖片部分來自網路。

加入戰隊

# 加入戰隊

微信公眾號

相關推薦

HashMap原理圖文並茂解讀這些注意一定瞭解

目錄 概述 屬性詳解 table entrySet size modCount threshold、loadFactor 原始

用了這麽久Linux,這些使用技巧可能知道!

sed notify 命令 read line onf itl too 小數點 浮點數 上周與大家分享了30個Linux使用技巧,但是還不夠!今天又總結了一些,在學習Linux的路上希望能幫到你。上篇:《30個必知的Linux命令技巧,你都掌握了嗎?》 31、監控目錄,新

乾貨|這些Python程式碼技巧,肯定知道

Python 是世界上最流行、熱門的程式語言之一,原因很多,比如: 易於學習 超高的通用性 具備大量模組和庫 本文將分享一些使用 Python 的技巧,順序按照 A-Z 排列。 進群進群:700341555可以獲取Python各類入門學習資料! 這是我的微信公眾號【Python程式設計之

微信域名防封跳轉系統的原理 微信域名防封的注意

什麼是微信域名防封跳轉系統啊? 微信域名防封跳轉系統是一個針對微信域名、微信網址、微信連結頻繁被封而產生的一門技術。它的主要工作原理是通過某種技術手段實時的查詢某個域名是否已經被微信攔截圖蔽封鎖,如果一旦確認被封就會自動跳轉到其它域名的過程。 例如:A網址--分享出去 ---使用者開啟A網址--

《暢銷的原理》:4星關於判斷與決策的研究的綜述

使用 src 分享 積極 對比 大量 性能 相同 效應 全書是關於人類的判斷與決策的心理學研究的綜述。有一些內容跟《屏幕上的聰明決策》重合。總體評價4星,還不錯,首先是因為是研究的綜述,每一個觀點都有出處,相對比較靠譜,其次有不少觀點還是比較新穎的。 以下是書中一些信

需要註冊的是我們還有一個是“交差集”?cross?join,?這種Join沒有辦法用文圖表示,因為其就是把表A和表B的數據進行一個N*M的組合,即笛卡爾積表達如下:

笛卡爾 tab 表達 但是 rom 產生 OS 是我 語法 還需要註冊的是我們還有一個是"交差集" cross join, 這種Join沒有辦法用文式圖表示,因為其就是把表A和表B的數據進行一個N*M的組合,即笛卡爾積。表達式如下: SELEC

hiveServer2 和 metastore的一點解讀

鏈接 等等 中間 tex ret lin 目前 vax using   剛看了hive官網的文檔,對於一些概念結合自己的經驗,似乎又多了一些理解,想一想還是記下來的好,一來我是個有些健忘的人,過一段時間即便忘了,循著這個帖子,也能快速把知識點抓起來;二來或許對別人也有些啟發

Atitit 程式語言的分類 v2 目錄 1.1. 基於代數劃分 第一代第三代4gl5gl自然語言 1 1.2. 按照程式設計正規化分類 . 命令式語言 .函式式語言...邏輯語言

Atitit 程式語言的分類 v2   目錄 1.1. 基於代數劃分   第一代。。。第三代。。4gl。。5gl自然語言 1 1.2. 按照程式設計正規化分類  . 命令式語言 .函式式語言...邏輯式語言 1 1.3. 命令式&

可持久化線段樹(主席樹)快速簡潔教程 圖文並茂 保證學會kth number例題

如果學不會也不要打我。 假設你會線段樹 開始! --- 主席樹也叫可持久化線段樹 顧名思義,它能夠儲存線段樹在每個時刻的版本。 什麼叫每個時刻的版本?你可能對一棵普通線段樹進行各種修改,這每種樣子就是我們所說的不同時刻的版本。 假設我們對線段樹進行單點修改,維護區間和。 每次修改操作中,只有logn個節點會被

在元素的裝載數量明確的時候HashMap的大小應該如何選擇

HashMap的效能問題。問題如下: java hashmap,如果確定只裝載100個元素,new HashMap(?)多少是最佳的,why? 要回答這個問題,首先得知道影響HashMap效能的引數有哪些。咱們翻翻JDK。 在JDK6中是這麼描述的: HashMap的例項有兩

【轉載】Spring @Async 原始碼解讀

正文 1.引子 開啟非同步任務使用方法: 1).方法上加@Async註解 2).啟動類或者配置類上@EnableAsync 2.原始碼解析 雖然spring5已經出來了,但是我們還是使用的spring4,本文就根據spring-context-4.3.14.RELEASE.jar來分析原

無法執行該操作,因為鏈接服務器 "XXX" 的 OLE DB 訪問接口 "SQLNCLI10" 無法啟動分布事務

設置 ted -c inf 服務器管理 網絡 右擊 window 存儲過程 在存儲過程中使用事務,並且使用鏈接服務器時,報以下錯誤: 無法執行該操作,因為鏈接服務器 "XXX" 的 OLE DB 訪問接口 "SQLNCLI10" 無法啟動分布式事務。 鏈接服務器"XXX

CRC校驗的原理和 CRC的用途

一、迴圈冗餘校驗碼(CRC) CRC校驗採用多項式編碼方法。被處理的資料塊可以看作是一個n階的二進位制多項式,由 。如一個8位二進位制數10110101可以表示為: 。多項式乘除法運算過程與普通代數多項式的乘除法相同。多項式的加減法運算以2為模,加減時不進,錯位,和邏輯異或運算一致。 採用CRC校驗時,傳送

【SQL】IDENTITY_INSERT 設定為 OFF 時,能為表 '***' 中的標識列插入顯

【前言】 今天在處理牛腩新增新聞資料時,將其中一天記錄複製為INsert 語句,在執行語句是報錯如下: 【解決方案】 在執行插入語句前,首先執行 -允許將顯式值插入表的標識列中 ON-允許 off - 不允許 SET IDENTITY_INS

記在vue-cli 2.0中使用typescript,從webpack ^3.0到^4.0的自殺升級

       最近抽了點時間弄了一下vue的服務端渲染(SSR)以及serviceWork和application的離線快取技術(PWA),於是就拿了以前的老專案去重寫了,但是老的有點落伍了是基於vue-cli 2.0的,完全就是在webpack的基礎上加

LVS NAT模式原理及配置詳解

NAT模式優缺點:因為請求與應答都要經過lvs伺服器,所以訪問量大的話lvs會形成瓶頸,一般要求10-20臺節點。注:(節點指後面的真實web伺服器)每臺節點伺服器的閘道器地址必須是lvs伺服器的內網地址。NAT模式支援對IP地址和埠進行轉換。即使用者請求的埠和真實伺服器的埠

PHP中的檔案載入和常量的語法以及指令寫法

指令式寫法::# 指令式語法就是將開始 ‘’{‘’換成 “:” 將結束 “}”換成 “end + if,,,for,,foreach.....”<?php header('content-type:text/html;charset=utf-8'); /

【集合框架】HashMap原理及原始碼解讀

本文加上個人理解,用自己的話表達集合框架及對HashMap細節的理解。 簡介 HashMap是一種利用鍵值對映儲存資料的資料結構,隨著jdk的發展,在jdk1.8中引入了紅黑樹的資料結構和擴容的優化。 Map類常用集合介紹 HashMap實現自java.uti

vue 什麼是漸進式 響應 意思Vue常用的指令VUE:跨域設定vue router 新視窗$router.push

Vue  是 輕量的 模型檢視檢視模型 框架。就是資料的雙向開發。資料驅動+元件化開發。漸進式框架。官網:cn.vuejs.org在讀 Vue2.0 文件的時候,介紹“Vue.js(讀音 /vjuː/,類似於 view)是一套構建使用者介面的漸進式框架。與其他重量級不同的是,

高併發環境下,HashMap可能出現的致命問題注意:是在jdk8以下版本

原文地址:https://blog.csdn.net/dgutliangxuan/article/details/78779448概念1:Rehash的概念? Rehash 是HashMap在擴容時候的一個步驟。HashMap的容量是有限的。當經過多次元素插入,使得HashM