Java 集合:HashMap(put方法的實現 與 雜湊衝突)
HashMap 概念
對於 Map ,最直觀就是理解就是鍵值對,對映,key-value 形式。一個對映不能包含重複的鍵,一個鍵只能有一個值。平常我們使用的時候,最常用的無非就是 HashMap。
HashMap 實現了 Map 介面,允許使用 null 值 和 null 鍵,並且不保證對映順序。
HashMap 有兩個引數影響效能:
- 初始容量:表示雜湊表在其容量自動增加之前可以達到多滿的一種尺度
- 載入因子:當雜湊表中的條目超過了容量和載入因子的乘積的時候,就會進行重雜湊操作。
如下成員變數原始碼:
static final float DEFAULT_LOAD_FACTOR = 0.75f; static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; transient Node<K,V>[] table;
可以看到,預設載入因子為 0.75, 預設容量為 1 << 4,也就是 16。載入因子過高,容易產生雜湊衝突,載入因子過小,容易浪費空間,0.75是一種折中。
另外,整個 HashMap 的實現原理可以簡單的理解成:當我們 put 的時候,首先根據 key 算出一個數值 x,然後在 table[x] 中存放我們的值。這樣有一個好處是,以後的 get 等操作的時間複雜度直接就是O(1),因為 HashMap 內部就是基於陣列的一個實現。
put 方法的實現 與 雜湊衝突
下面再結合程式碼重點分析下 HashMap 的 put 方法的內部實現 和 雜湊衝突的解決辦法:
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; }
首先我們看到 hash(key)
這個就是表示要根據 key 值算出一個數值,以此來決定在 table 陣列的哪一個位置存放我們的數值。(Ps:這個 hash(key)
方法 也是大有講究的,會嚴重影響效能,實現得不好會讓 HashMap 的 O(1) 時間複雜度降到 O(n),在JDK8以下的版本中帶來災難性影響。它需要保證得出的數在雜湊表中的均勻分佈,目的就是要減少雜湊衝突)
重要說明一下:
- **JDK8 中雜湊衝突過多,連結串列會轉紅黑樹,時間複雜度是O(logn),不會是O(n) **
- **JDK8 中雜湊衝突過多,連結串列會轉紅黑樹,時間複雜度是O(logn),不會是O(n) **
- **JDK8 中雜湊衝突過多,連結串列會轉紅黑樹,時間複雜度是O(logn),不會是O(n) **
然後,我們再看到:
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
......
這就表示,如果沒有 雜湊衝突,那麼就可以放入資料 tab[i] = newNode(hash, key, value, null);
如果有雜湊衝突,那麼就執行 else 需要解決雜湊衝突。
那麼放入資料 其實就是 建立一個 Node 節點,該 Node節點有屬性 key,value,分別儲存我們的 key 值 和 value 值,然後再把這個 Node 節點放入到 table 陣列中,並沒有什麼神祕的地方。
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
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;
}
上述可以看到 Node 節點中 有一個 Node<K,V> next;
,其實仔細思考下就應該知道這個是用來解決雜湊衝突的。下面再看看是如何解決雜湊衝突的:
雜湊衝突:通俗的講就是首先我們進行一次 put 操作,算出了我們要在 table 陣列的 x 位置放入這個值。那麼下次再進行一個 put 操作的時候,又算出了我們要在 table 陣列的 x 位置放入這個值,那之前已經放入過值了,那現在怎麼處理呢?
其實就是通過連結串列法進行解決。
首先,如果有雜湊衝突,那麼:
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
需要判斷 兩者的 key 是否一樣的,因為 HashMap 不能加入重複的鍵。如果一樣,那麼就覆蓋,如果不一樣,那麼就先判斷是不是 TreeNode 型別的:
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
這裡表示 是不是現在已經轉紅黑樹了(在大量雜湊衝突的情況下,連結串列會轉紅黑樹),一般我們小資料的情況下,是不會轉的,所以這裡暫時不考慮這種情況(Ps:本人也沒太深入研究紅黑樹,所以就不說這個了)。
如果是正常情況下,會執行下面的語句來解決雜湊衝突:
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;
}
這裡其實就是用連結串列法來解決。並且:
- 衝突的節點放在連結串列的最下面。
- 衝突的節點放在連結串列的最下面。
- 衝突的節點放在連結串列的最下面。
因為 首先有:p = tab[i = (n - 1) & hash]
,再 for 迴圈,然後有 if
((e = p.next) == null) {
,並且如果 當前節點的下一個節點有值的話,那麼就 p = e;
,這就說明了放在最下面。
強烈建議自己拿筆拿紙畫畫。
總結
- 一個對映不能包含重複的鍵,一個鍵只能有一個值。允許使用 null 值 和 null 鍵,並且不保證對映順序。
- HashMap 解決衝突的辦法先是使用連結串列法,然後如果雜湊衝突過多,那麼會把連結串列轉換成紅黑樹,以此來保證效率。
- 如果出現了雜湊衝突,那麼新加入的節點放在連結串列的最後面。
相關推薦
Java 集合:HashMap(put方法的實現 與 雜湊衝突)
HashMap 概念 對於 Map ,最直觀就是理解就是鍵值對,對映,key-value 形式。一個對映不能包含重複的鍵,一個鍵只能有一個值。平常我們使用的時候,最常用的無非就是 HashMap。 HashMap 實現了 Map 介面,允許使用 null 值 和 nu
Java集合:HashMap原始碼剖析
一、HashMap概述 HashMap基於雜湊表的 Map 介面的實現。此實現提供所有可選的對映操作,並允許使用 null 值和 null 鍵。(除了不同步和允許使用 null 之外,HashMap 類與 Hashtable 大致相同。)此類不保證對映的順序,特別是它不保證該順序恆久
Java 集合:TreeMap工作原理及實現
前言 本文轉載自:點這裡,該部落格非常不錯,建議前去看看。 正文 1. 概述 A Red-Black tree based NavigableMap implementation. The map is sorted according to the natura
HashMap中put()方法實現原理
突然想解剖HashMap實現原理,Map連結串列的作者原始碼如何實現?也可以豐富一下自己的程式設計思想,也想讓讀者看見如何觀看別人原始碼的思路和方法。所以心血來潮的我,就來解析HashMap底層原理! 送給讀者的話:一個合格的程式設計師一定要學會觀看別人程式碼
Java集合:LinkedList (JDK1.8 原始碼解讀)
LinkedList介紹 還是和ArrayList同樣的套路,顧名思義,linked,那必然是基於連結串列實現的,連結串列是一種線性的儲存結構,將儲存的資料存放在一個儲存單元裡面,並且這個儲存單元裡面還維護了下一個儲存單元的地址。在LinkedList的連結串列儲存單元中,不僅存放了下一個儲存單元的地址,還存
修改tomcat埠號(tomcat埠號與其他應用衝突)
在Tomcat的conf資料夾裡有個server.xml檔案,修改裡面的 <Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000"
Java容器(四):HashMap(Java 7)的實現原理
一、HashMap的定義和建構函式 public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, S
Java 集合深入理解(17):HashMap 在 JDK 1.8 後新增的紅黑樹結構
上篇文章我們介紹了 HashMap 的主要特點和關鍵方法原始碼解讀,這篇文章我們介紹 HashMap 在 JDK1.8 新增樹形化相關的內容。 讀完本文你將瞭解到: 傳統 HashMap 的缺點 JDK 1.8 以前 HashMap 的實
java集合HashMap的put方法
首先我們需要對hashmap初始換容器大小 HashMap< String, String > hashMap = new HashMap<>(16); 如果我們的寫的數字小於MAXIMUM_CAPACITY = 1 &
Java:利用遞迴方法實現角谷定理
問題描述: 角谷定理。輸入一個自然數,若為偶數,則把它除以2,若為奇數,則把它乘以3加1。經過如此有限次運算後,總可以得到自然數值1。求經過多少次可得到自然數1。 如:輸入22, 輸出 22 11 34 17 52 26 1
java集合之----HashMap原始碼分析(基於JDK1.7與1.8)
一、什麼是HashMap 百度百科這樣解釋: 簡而言之,HashMap儲存的是鍵值對(key和value),通過key對映到value,具有很快的訪問速度。HashMap是非執行緒安全的,也就是說在多執行緒併發環境下會出現問題(死迴圈) 二、內部實現 (1)結構 HashM
hashmap實現原理(雜湊值計算,put方法,擴容) jdk1.8帶來的優化 hashmap併發安全 ConcurrentHashMap
HashMap的原始碼,實現原理,JDK8中對HashMap做了怎樣的優化。 ArrayList和LinkedList的優缺點——陣列的特點是:定址容易,插入和刪除困難;而連結串列的特點是:定址困難,插入和刪除容易。 hashmap底層
Java 集合原始碼解析(1):Iterator
Java 提供的 集合類都在 Java.utils 包下,其中包含了很多 List, Set, Map, Queue… 它們的關係如下面這張類圖所示: 可以看到,Java 集合主要分為兩類:Collection 和 Map. 而 Collection 又繼承了 Iter
Java 集合深入理解(4):List 介面
在 Java 集合深入理解:Collection 中我們熟悉了 Java 集合框架的基本概念和優點,也瞭解了根介面之一的 Collection,這篇文章來加深 Collection 的子介面之一 List 的熟悉。 List 介面 一個 List 是一個元素有
井字棋遊戲實現-java(低階方法實現)
import java.util.Scanner; public class JingGame { final static int LENGTH=3; //棋局的長度 final static int NUM_FOR_WIN=3;//幾個子連起來贏
JAVA集合:迭代器實現原理
前言 在JAVA的學習和開發中,經常需要對集合或者陣列進行遍歷,遍歷的方法有多種:for迴圈、foreach、迭代器。 for迴圈的實現簡單明瞭,就是迴圈下標,判斷邊界,取到每個下標的資料。至於foreach和迭代器,其實foreach在反編譯以後可以看到就是迭代器實現的,因此,今天來學
散列表(四):衝突處理的方法之開地址法(二次探測再雜湊的實現)
#include "hash.h"#include "common.h"#include <assert.h>typedef enum entry_status { EMPTY, ACTIVE, DELETED } entry_status_t;typedef struct
深度解析Java 8:JDK1.8 AbstractQueuedSynchronizer的實現分析(上)
前言 Java中的FutureTask作為可非同步執行任務並可獲取執行結果而被大家所熟知。通常可以使用future.get()來獲取執行緒的執行結果,線上程執行結束之前,get方法會一直阻塞狀態,直到call()返回,其優點是使用執行緒非同步執行任務的情況下還可以獲取到
(好使)用Java集合中的Collections.sort方法對list排序的兩種方法
ret = String.valueOf(m2.invoke(((E)b), null).toString().length()).compareTo(String.valueOf(m1.invoke(((E)a), null).toString().length())); if
Java 集合深入理解(3):Collection
今天心情有點粉,來學學 Collection 吧! 什麼是集合? 集合,或者叫容器,是一個包含多個元素的物件; 集合可以對資料進行儲存,檢索,操作; 它們可以把許多個體組織成一個整體: 比如一副撲克牌(許多牌組成的集合); 比如一個電話本