最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java源碼HashMap源碼的使用分析

 更新時間:2025年05月16日 15:33:12   作者:二六八  
這篇文章主要介紹了Java源碼HashMap源碼的使用分析,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教

Java 源碼 HashMap源碼分析

1 初始容量

    /**
     * The default initial capacity - MUST be a power of two.
     * 默認(rèn)的初始容量,必須為2的冪
     */
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

容量表示哈希表中槽的數(shù)量(即哈希數(shù)組的長度),初始容量是創(chuàng)建哈希表時的容量(從構(gòu)造函數(shù)中可以看出,如果不指明,則默認(rèn)為16)

無論我們指定的容量為多少,構(gòu)造方法都會將實際容量設(shè)為不小于指定容量的2的次方的一個數(shù),且最大值不能超過2的30次方

2 加載因子

    /**
     * The load factor used when none specified in constructor.
     * 在構(gòu)造函數(shù)中沒有指定時使用的加載因子
     */
    static final float DEFAULT_LOAD_FACTOR = 0.75f;

哈希表在其容量自動增加之前可以達(dá)到多滿的一種尺度,當(dāng)哈希表中的條目數(shù)超出了加載因子與當(dāng)前容量的乘積時,則要對該哈希表進(jìn)行 resize 操作(即擴(kuò)容)。

  • 如果加載因子越大,對空間的利用更充分,但是查找效率會降低(鏈表長度會越來越長);
  • 如果加載因子太小,那么表中的數(shù)據(jù)將過于稀疏(很多空間還沒用,就開始擴(kuò)容了),對空間造成嚴(yán)重浪費。
  • 如果我們在構(gòu)造方法中不指定,則系統(tǒng)默認(rèn)加載因子為0.75,這是一個比較理想的值,一般情況下我們是無需修改的。

3 單向鏈表中的數(shù)據(jù)節(jié)點

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;
        }

        public final K getKey()        { return key; }
        public final V getValue()      { return value; }
        public final String toString() { return key + "=" + value; }

        public final int hashCode() {
            return Objects.hashCode(key) ^ Objects.hashCode(value);
        }

        public final V setValue(V newValue) {
            V oldValue = value;
            value = newValue;
            return oldValue;
        }

        public final boolean equals(Object o) {
        }
    }

4 紅黑樹結(jié)構(gòu)

在jdk1.8版本后,java對HashMap做了改進(jìn),當(dāng)鏈表長度必須大于 2 ,并且應(yīng)該至少為 8 的時候,將后面的數(shù)據(jù)存在紅黑樹中,以加快檢索速度,我們接下來講一下紅黑樹。

   /**
    * The bin count threshold for using a tree rather than list for a
    * bin.  Bins are converted to trees when adding an element to a
    * bin with at least this many nodes. The value must be greater
    * than 2 and should be at least 8 to mesh with assumptions in
    * tree removal about conversion back to plain bins upon
    * shrinkage.
    */
   static final int TREEIFY_THRESHOLD = 8;

5 概述

概括的說,HashMap 是一個關(guān)聯(lián)數(shù)組、哈希表,它是線程不安全的,允許key為null,value為null。遍歷時無序。

其底層數(shù)據(jù)結(jié)構(gòu)是數(shù)組稱之為哈希桶,每個桶里面放的是鏈表,鏈表中的每個節(jié)點,就是哈希表中的每個元素。

在JDK8中,當(dāng)鏈表長度達(dá)到8,會轉(zhuǎn)化成紅黑樹,以提升它的查詢、插入效率,它實現(xiàn)了Map<K,V>, Cloneable, Serializable接口。

因其底層哈希桶的數(shù)據(jù)結(jié)構(gòu)是數(shù)組,所以也會涉及到擴(kuò)容的問題。

當(dāng)HashMap的容量達(dá)到threshold域值時,就會觸發(fā)擴(kuò)容。擴(kuò)容前后,哈希桶的長度一定會是2的次方。

這樣在根據(jù)key的hash值尋找對應(yīng)的哈希桶時,可以用位運算替代取余操作,更加高效。

而key的hash值,并不僅僅只是key對象的hashCode()方法的返回值,還會經(jīng)過擾動函數(shù)的擾動,以使hash值更加均衡。

因為hashCode()是int類型,取值范圍是40多億,只要哈希函數(shù)映射的比較均勻松散,碰撞幾率是很小的。

但就算原本的hashCode()取得很好,每個key的hashCode()不同,但是由于HashMap的哈希桶的長度遠(yuǎn)比hash取值范圍小,默認(rèn)是16,所以當(dāng)對hash值以桶的長度取余,以找到存放該key的桶的下標(biāo)時,由于取余是通過與操作完成的,會忽略hash值的高位。因此只有hashCode()的低位參加運算,發(fā)生不同的hash值,但是得到的index相同的情況的幾率會大大增加,這種情況稱之為hash碰撞。 即,碰撞率會增大。

擾動函數(shù)就是為了解決hash碰撞的。它會綜合hash值高位和低位的特征,并存放在低位,因此在與運算時,相當(dāng)于高低位一起參與了運算,以減少hash碰撞的概率。(在JDK8之前,擾動函數(shù)會擾動四次,JDK8簡化了這個操作)

擴(kuò)容操作時,會new一個新的Node數(shù)組作為哈希桶,然后將原哈希表中的所有數(shù)據(jù)(Node節(jié)點)移動到新的哈希桶中,相當(dāng)于對原哈希表中所有的數(shù)據(jù)重新做了一個put操作。所以性能消耗很大,可想而知,在哈希表的容量越大時,性能消耗越明顯。

  • 擴(kuò)容時,如果發(fā)生過哈希碰撞,節(jié)點數(shù)小于8個。則要根據(jù)鏈表上每個節(jié)點的哈希值,依次放入新哈希桶對應(yīng)下標(biāo)位置。
  • 因為擴(kuò)容是容量翻倍,所以原鏈表上的每個節(jié)點,現(xiàn)在可能存放在原來的下標(biāo),即low位, 或者擴(kuò)容后的下標(biāo),即high位。 high位= low位+原哈希桶容量
  • 如果追加節(jié)點后,鏈表數(shù)量>=8,則轉(zhuǎn)化為紅黑樹

由迭代器的實現(xiàn)可以看出,遍歷HashMap時,順序是按照哈希桶從低到高,鏈表從前往后,依次遍歷的。屬于無序集合。

6 put操作

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;
    }

7 重寫equals方法需同時重寫hashCode方法

關(guān)于HashMap的源碼分析就介紹到這兒了,最后我們再聊聊老生常談的一個問題,各種資料上都會提到,“重寫equals時也要同時覆蓋hashcode”,我們舉個小例子來看看,如果重寫了equals而不重寫hashcode會發(fā)生什么樣的問題

/**
 * Created by chengxiao on 2016/11/15.
 */
public class MyTest {
    private static class Person{
        int idCard;
        String name;

        public Person(int idCard, String name) {
            this.idCard = idCard;
            this.name = name;
        }
        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (o == null || getClass() != o.getClass()){
                return false;
            }
            Person person = (Person) o;
            //兩個對象是否等值,通過idCard來確定
            return this.idCard == person.idCard;
        }

    }
    public static void main(String []args){
        HashMap<Person,String> map = new HashMap<Person, String>();
        Person person = new Person(1234,"喬峰");
        //put到hashmap中去
        map.put(person,"天龍八部");
        //get取出,從邏輯上講應(yīng)該能輸出“天龍八部”
        System.out.println("結(jié)果:"+map.get(new Person(1234,"蕭峰")));
    }
}

如果我們已經(jīng)對HashMap的原理有了一定了解,這個結(jié)果就不難理解了。盡管我們在進(jìn)行g(shù)et和put操作的時候,使用的key從邏輯上講是等值的(通過equals比較是相等的),但由于沒有重寫hashCode方法,所以put操作時,key(hashcode1)–>hash–>indexFor–>最終索引位置 ,而通過key取出value的時候 key(hashcode1)–>hash–>indexFor–>最終索引位置,由于hashcode1不等于hashcode2,導(dǎo)致沒有定位到一個數(shù)組位置而返回邏輯上錯誤的值null(也有可能碰巧定位到一個數(shù)組位置,但是也會判斷其entry的hash值是否相等,上面get方法中有提到。)

所以,在重寫equals的方法的時候,必須注意重寫hashCode方法,同時還要保證通過equals判斷相等的兩個對象,調(diào)用hashCode方法要返回同樣的整數(shù)值。而如果equals判斷不相等的兩個對象,其hashCode可以相同(只不過會發(fā)生哈希沖突,應(yīng)盡量避免)。

8 數(shù)組的最大容量

    /**
    * The maximum capacity, used if a higher value is implicitly specified
    * by either of the constructors with arguments.
    * MUST be a power of two <= 1<<30.
    */
static final int MAXIMUM_CAPACITY = 1 << 30;

最大容量(必須是2的冪且小于2的30次方,傳入容量過大將被這個值替換)

如果傳入的容量cap不是2的冪次方,則找出"大于cap"的最小的2的冪

9 存放的最大元素數(shù)量

顯然是 Integer.MAX_VALUE

if (oldCap >= MAXIMUM_CAPACITY) {
    threshold = Integer.MAX_VALUE;
    return oldTab;
}

10 擴(kuò)容

  • 當(dāng)元素超過數(shù)組長度的 75% 就會發(fā)生擴(kuò)容,既長度增加一倍。
  • 默認(rèn)的數(shù)組長度 DEFAULT _INITIAL_ CAPACITY = 16 ,默認(rèn)的負(fù)載因子DEFAULT LOAD FACTOR =0.75;當(dāng)鍵值對數(shù)量超過 16 * 0.75 = 12 時,就會觸發(fā)擴(kuò)容導(dǎo)致數(shù)組長度變?yōu)?16 * 2 = 32

注意:擴(kuò)容后,每個鍵值對數(shù)據(jù)存儲的索引下標(biāo)需要重新計算。通過公式:keyHash&(newLength-1)。結(jié)果會變成:newIndex = oldIndex + 擴(kuò)容增加的長度。

final Node<K,V>[] resize() {
    Node<K,V>[] oldTab = table;
    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)
            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);
    }
    if (newThr == 0) {
        float ft = (float)newCap * loadFactor;
        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;
    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;
}

總結(jié)

以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • Spring AOP實現(xiàn)功能權(quán)限校驗功能的示例代碼

    Spring AOP實現(xiàn)功能權(quán)限校驗功能的示例代碼

    本篇文章主要介紹了Spring AOP實現(xiàn)功能權(quán)限校驗功能的示例代碼,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-12-12
  • JDBC數(shù)據(jù)庫連接步驟解析

    JDBC數(shù)據(jù)庫連接步驟解析

    這篇文章主要介紹了JDBC數(shù)據(jù)庫連接步驟解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-02-02
  • SpringBoot中文件下載與JSON響應(yīng)沖突的深度解析與解決方案

    SpringBoot中文件下載與JSON響應(yīng)沖突的深度解析與解決方案

    在Spring Boot開發(fā)中,我們經(jīng)常遇到需要同時處理文件下載和JSON響應(yīng)的場景,很多開發(fā)者會遇到這樣一個困惑:明明已經(jīng)正確配置了文件下載,但瀏覽器要么無法下載文件,要么返回的JSON數(shù)據(jù)混亂,本文將深入分析這個問題的本質(zhì),并提供多種優(yōu)雅的解決方案
    2026-03-03
  • JavaWeb 入門:Hello Servlet

    JavaWeb 入門:Hello Servlet

    這篇文章主要介紹了Servlet開發(fā)JavaWeb工程示例詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-07-07
  • Spring中@Autowired注解在不同方法的寫法示例

    Spring中@Autowired注解在不同方法的寫法示例

    這篇文章主要為大家介紹了Spring中@Autowired注解在不同方法的寫法示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-01-01
  • Spring Cloud使用Feign實現(xiàn)Form表單提交的示例

    Spring Cloud使用Feign實現(xiàn)Form表單提交的示例

    本篇文章主要介紹了Spring Cloud使用Feign實現(xiàn)Form表單提交的示例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-03-03
  • Java使用反射調(diào)用方法示例

    Java使用反射調(diào)用方法示例

    這篇文章主要介紹了Java使用反射調(diào)用方法,結(jié)合實例形式分析了java使用反射調(diào)用對象方法的相關(guān)操作技巧,需要的朋友可以參考下
    2019-07-07
  • MVC中的攔截器實現(xiàn)案例解析

    MVC中的攔截器實現(xiàn)案例解析

    攔截器(Interceptor)依賴于web框架,在SpringMVC中就是依賴于SpringMVC框架,本文介紹MVC中的攔截器實現(xiàn)案例,感興趣的朋友跟隨小編一起看看吧
    2026-04-04
  • 詳細(xì)分析Java內(nèi)部類——局部內(nèi)部類

    詳細(xì)分析Java內(nèi)部類——局部內(nèi)部類

    這篇文章主要介紹了Java局部內(nèi)部類的相關(guān)資料,幫助大家更好的理解和學(xué)習(xí)Java 內(nèi)部類的知識,感興趣的朋友可以了解下
    2020-08-08
  • java基于odbc連接oracle的實現(xiàn)方法

    java基于odbc連接oracle的實現(xiàn)方法

    這篇文章主要介紹了java基于odbc連接oracle的實現(xiàn)方法,結(jié)合實例形式分析了連接操作的具體步驟與相關(guān)實現(xiàn)技巧,需要的朋友可以參考下
    2016-09-09

最新評論

盐山县| 抚宁县| 营口市| 南乐县| 云龙县| 永年县| 堆龙德庆县| 江北区| 双峰县| 樟树市| 美姑县| 醴陵市| 庆云县| 察隅县| 肥乡县| 建水县| 宁强县| 甘谷县| 万安县| 随州市| 磐石市| 中西区| 淮滨县| 太原市| 灵山县| 阿拉善盟| 玛多县| 华容县| 宿迁市| 正蓝旗| 大竹县| 乐昌市| 宜城市| 沁水县| 桃江县| 桓仁| 邓州市| 广宗县| 凤冈县| 大化| 尤溪县|