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 元件用於顯示文字文件,包含純文