目錄
使用簡介
初始化
HashMap#put()
HashMap#resize()
HashMap#get()
首頁 Java java教程 帶你們來了解Java8 HashMap源碼解析的步驟

帶你們來了解Java8 HashMap源碼解析的步驟

Sep 13, 2018 pm 01:56 PM
hashmap 原始碼解析

用過java的朋友或多或少對HashMap有一些不了解的地方,文章內容很緊湊,希望大家堅持學習哦

前言

Java7中的HashMap和Java8中的HashMap不太一樣,Java7中的HashMap主要是由數組鍊錶組成的,而Java8中的HashMap是由數組鍊錶紅黑樹組成的,當鍊錶的長度超過8個時,就會轉為紅黑樹,降低查找時的時間複雜度,進而提高效率。這裡主要分析的是Java8中的HashMap。

使用簡介

在日常開發中,我們在使用HashMap的時候,有以下兩種初始化方式:
   1、透過new HashMap()不指定初始值大小;
   2、透過new HashMap<>(int initialCapacity)指定初始值大小。

初始化

//  数组的默认初始容量
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
//  最大容量
static final int MAXIMUM_CAPACITY = 1 << 30;
//  默认负载因子(用来控制阈值和容量,当数组已经存放的容量超过了阈值时,容量会扩大一倍
//  阈值 = 最大容量 * 负载因子)
static final float DEFAULT_LOAD_FACTOR = 0.75f;
//  默认链表的最大长度(当链表的长度超过8时会被转换为红黑树)
static final int TREEIFY_THRESHOLD = 8;
//  使用第一种初始化方式时,默认初始化容量是2的4次
public HashMap() {    
this.loadFactor = DEFAULT_LOAD_FACTOR; 
}
public HashMap(int initialCapacity) {   
     this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
     public HashMap(int initialCapacity, float loadFactor) {   
      //  不能小于0
    if (initialCapacity < 0) 
             throw new IllegalArgumentException("Illegal initial capacity: initialCapacity); 
                //  超过2的30次方,则最大容量为2的30次方
    if (initialCapacity > MAXIMUM_CAPACITY)
        initialCapacity = MAXIMUM_CAPACITY;  
                  if (loadFactor <= 0 || Float.isNaN(loadFactor))   
                       throw new IllegalArgumentException("Illegal load factor: loadFactor);
                           this.loadFactor = loadFactor;   
                 //  计算阈值(在第一次put值的时候,会在resize()方法中重新计算)
    this.threshold = tableSizeFor(initialCapacity);
}
登入後複製

HashMap#put()

public V put(K key, V value) 
{    return putVal(hash(key), key, value, false, true);
}
登入後複製
static final int hash(Object key) {  
  int h;    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
登入後複製

hash()方法主要是對存入的key值進行判斷,如果為null,則傳回0;不為null,則傳回key的hash值與hash值無符號右移16位元以後的值進行位元異或的結果(有點繞口)。由此可以看出HashMap的key值可以為null。

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;   
     //  第一次put值时,会初始化当前数组长度,如果没有指定,则默认为16
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;  
          //  找到在数组中对应的下标,如果该位置没有值,那么直接初始化一个Node放在此位置
             // 由&运算可以确保(n - 1) & hash一定是小于数组容量
        tab[i] = newNode(hash, key, value, null);  
          else {
        Node<K,V> e; K k;        
        //  如果key值已经存在,则取出这个节点
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;       
             //  如果当前key值的节点是红黑树,则调用红黑树的插值算法
        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);        
                   //  如果链表的长度大于等于8,则将链表转换为红黑树
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1sttreeifyBin(tab, hash);       
                                 break;
                }               
                 //  如果在链表的节点中存在相同的key,则结束循环
                if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))                    break;
                p = e;
            }
        }        //  如果存在相同的key值,则重新赋值,并且返回旧值
        if (e != null) { 
        // existing mapping for key
            V oldValue = e.value;           
             //  由源码可知onlyIfAbsent默认false 
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            afterNodeAccess(e);  
          return oldValue;
        }
    }
    ++modCount;   
     //  如果数组已经容纳的长度超过了设定的阈值,则会对该数组进行扩容,每次扩容是之前长度的两倍
    if (++size > threshold)
        resize();
    afterNodeInsertion(evict);   
     //  每一个不同的key值第一次put的时候返回null。
    return null;
}
登入後複製

HashMap#resize()

resize()方法主要作用是初始化陣列或對陣列進行擴容計算。

    final Node<K,V>[] resize() {    
       //  备份原始数组
    Node<K,V>[] oldTab = table;    
    //  第一次put值的时候为0
    int oldCap = (oldTab == null) ? 0 : oldTab.length;   
     //  如果没有指定初始值大小,第一次put值的时候阈值为0
    int oldThr = threshold;    int newCap, newThr = 0;   
     //  如果数组不为null且长度不为0,则会
    if (oldCap > 0) {       
     //  如果长度大于等2的30次方,则默认阈值为int的最大值(即2的31次方减1)
        if (oldCap >= MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;            
            return oldTab;
        }       
         //  如果将数组长度扩大一倍后的值小于2的30次方并且数组之前的长度大于等于2的4次方,则将阈值扩大
          //  一倍,否则阈值会在下面的if (newThr == 0)中进行赋值
        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&oldCap >= DEFAULT_INITIAL_CAPACITY)     
               //  将阈值扩大一倍
            newThr = oldThr << 1; // double threshold
          }   
     //  如果使用new HashMap(int initialCapacity)初始化,则第一次put值时会进入这里
    else if (oldThr > 0) 
        initial capacity was placed in threshold
        newCap = oldThr;  
          //  如果使用new HashMap()初始化,则第一次put值时会进入这里
    else {             
          zero initial threshold signifies using defaults
        //  默认数组大小是2的4次方
        newCap = DEFAULT_INITIAL_CAPACITY;        
        //  默认负载因子是0.75,即默认阈值为12
        newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
    }   
     //  只有以下两种情况会进入到if判断中:
      //  1、在使用new HashMap(int initialCapacity)初始化,并且第一次put值的时候
      //   2、对数组进行扩容且数组的原始长度小于2的4次方的时候
    if (newThr == 0) {       
     //  根据指定的数组大小和负载因子乘积得到阈值
        float ft = (float)newCap * loadFactor;       
      //  如果数组大小和阈值都小于2的20次方,则确定阈值
        newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                    (int)ft : Integer.MAX_VALUE);
    }
    threshold = newThr;   
     @SuppressWarnings({"rawtypes","unchecked"})   
      //  用新的数组大小初始化新的数组
        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
    table = newTab;   
     //  如果是第一次初始化,则直接返回newTab。如果不是则会进行数据的迁移操作
    if (oldTab != null) {  
          //  遍历数组
        for (int j = 0; j < oldCap; ++j) {
            Node<K,V> e;            
            if ((e = oldTab[j]) != null) {         
                   //  将已经被取出的位置置空
                oldTab[j] = null;       
                         //  如果数组该位置只是单个节点,那么直接赋值
                if (e.next == null)
                    newTab[e.hash & (newCap - 1)] = e;   
                                 //  如果数组该位置是红黑树
                else if (e instanceof TreeNode)
                    ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);    
                                //  如果数组该位置是链表,保证原始的循序进行迁移
                else { 
                      preserve order
                    Node<K,V> loHead = null, loTail = null;
                    Node<K,V> hiHead = null, hiTail = null;
                    Node<K,V> next;
                    do {
                        next = e.next;                        
                        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;
}
登入後複製

由resize()方法可以看出,負載因子決定了陣列的容量和使用程度。
負載因子越大則陣列的填裝密度越高,也就是能容納更多的元素。但由於陣列插入或刪除元素的時間複雜度O(n),所以索引的效率會變低。
但是,負載因子越小則數組中的填裝密度越稀疏,此時會空間的浪費,但是此時索引效率高(用空間換取時間)。

HashMap#get()

相較於put方法,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;    //  首先判断数组不为null以及长度大于0并且对应位置节点不为null
    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;        //  如果节点的下一个节点不为null
        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;
}
登入後複製

相關推薦:

HashMap原始碼剖析

詳解Java集合框架HashSet和HashMap原始碼剖析(圖)

以上是帶你們來了解Java8 HashMap源碼解析的步驟的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

<🎜>:泡泡膠模擬器無窮大 - 如何獲取和使用皇家鑰匙
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
北端:融合系統,解釋
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
Mandragora:巫婆樹的耳語 - 如何解鎖抓鉤
3 週前 By 尊渡假赌尊渡假赌尊渡假赌

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

熱門話題

Java教學
1665
14
CakePHP 教程
1424
52
Laravel 教程
1322
25
PHP教程
1270
29
C# 教程
1249
24
hashmap的擴容機制是什麼 hashmap的擴容機制是什麼 Mar 15, 2023 pm 03:39 PM

hashmap的擴容機制是:重新計算容量,用新的陣列取代原來的陣列。重新計算原始數組的所有資料並插入一個新數組,然後指向新數組;如果數組在容量擴展前已達到最大值,則直接將閾值設為最大整數返回。

基於Java HashMap,如何解決插入重複的Key值問題 基於Java HashMap,如何解決插入重複的Key值問題 May 09, 2023 am 10:52 AM

javaHashMap插入重複Key值要在HashMap中插入重複的值,首先要先弄清楚HashMap裡面是怎麼存放元素的。 put方法Map裡面存放的每一個元素都是key-value這樣的鍵值對,而且都是透過put方法進行新增的,而且相同的key在Map中只會有一個與之關聯的value存在。 put方法在Map中的定義如下。 Vput(Kkey,Vvalue);put()方法實作:首先hash(key)得到key的hashcode(),hashmap根據所得的hashcode找到要插入的位置所在的鏈,

如何使用HashMap類別的put()方法將鍵值對插入到HashMap中 如何使用HashMap類別的put()方法將鍵值對插入到HashMap中 Jul 26, 2023 pm 11:53 PM

如何使用HashMap類別的put()方法將鍵值對插入到HashMap中HashMap是Java集合框架中的一個非常重要的類,它提供了一種儲存鍵值對的方式。在實際開發中,我們經常需要在HashMap中插入鍵值對,透過使用HashMap類別的put()方法可以輕鬆實現這一目標。 HashMap的put()方法的簽章如下:Vput(Kkey,Vvalue)

Java文件解讀:HashMap類別的containsKey()方法用法詳解 Java文件解讀:HashMap類別的containsKey()方法用法詳解 Nov 04, 2023 am 08:12 AM

Java文件解讀:HashMap類別的containsKey()方法用法詳解,需要具體程式碼範例引言:HashMap是Java中常用的資料結構,它提供了高效率的儲存和尋找功能。其中的containsKey()方法用來判斷HashMap中是否包含指定的鍵。本文將詳細解讀HashMap類別的containsKey()方法的使用方式,並提供具體的程式碼範例。一、cont

Java使用HashMap類別的putAll()函數將一個Map加入另一個Map Java使用HashMap類別的putAll()函數將一個Map加入另一個Map Jul 24, 2023 am 09:36 AM

Java使用HashMap類別的putAll()函數將一個Map加入到另一個Map中Map是Java中常用的資料結構,用來表示鍵值對的集合。在Java的集合框架中,HashMap是一個常用的實作類別。它提供了putAll()函數,用於將一個Map添加到另一個Map中,以方便實現資料的合併和拷貝。本文將介紹putAll()函數的使用方法,並提供對應的程式碼範例。首先,

java中LinkedHashMap和HashMap差別是什麼 java中LinkedHashMap和HashMap差別是什麼 May 02, 2023 am 08:31 AM

1.說明Map基本上可以使用HashMap,但是HashMap有一個問題,那就是迭代HashMap的順序不是HashMap放置的順序,就是無序。 HashMap的這個缺點往往會帶來麻煩,因為有些場景我們期待一個有序的Map,那就是LinkedHashMap。 2.區別實例publicstaticvoidmain(String[]args){Mapmap=newLinkedHashMap();map.put("apple","蘋果");map.put(&quot

Java單例模式怎麼利用HashMap實作快取數據 Java單例模式怎麼利用HashMap實作快取數據 May 13, 2023 am 09:43 AM

一、單例模式是什麼?單例模式是一種物件建立模式,它用於產生一個物件的具體實例,它可以確保系統中一個類別只產生一個實例。 Java裡面實作的單例是一個虛擬機器的範圍,因為裝載類別的功能是虛擬機器的,所以一個虛擬機器在透過自己的ClassLoad裝載實作單例類別的時候就會建立一個類別的實例。在Java語言中,這樣的行為能帶來兩大好處:1.對於頻繁使用的對象,可以省略創建對象所花費的時間,這對於那些重量級對象而言,是非常可觀的一筆系統開銷; 2.由於new操作的次數減少,因而對系統記憶體的使用頻率也會降低,這將減輕GC壓

Java Map 效能優化揭秘:讓你的資料操作更快速、更有效率 Java Map 效能優化揭秘:讓你的資料操作更快速、更有效率 Feb 20, 2024 am 08:31 AM

JavaMap是Java標準函式庫中常用的資料結構,它以鍵值對的形式儲存資料。 Map的效能對於應用程式的運作效率至關重要,如果Map的效能不佳,可能會導致應用程式運作緩慢,甚至崩潰。 1.選擇合適的Map實作Java提供了多種Map實現,包括HashMap、TreeMap和LinkedHashMap。每種Map實作都有各自的優缺點,在選擇Map實作時,需要根據應用程式的特定需求來選擇合適的實作。 HashMap:HashMap是最常用的Map實現,它使用哈希表來儲存數據,具有較快的插入、刪除和查找速度

See all articles