国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

Java集合_HashMap篇

MoAir / 1200人閱讀

摘要:如果不重復(fù),判斷是否是類(lèi)型,如果是紅黑樹(shù),直接插入。條件為時(shí)執(zhí)行鏈表轉(zhuǎn)紅黑樹(shù),然后插入。為了避免尾部遍歷。添加元素時(shí),如果超過(guò)閾值,就要進(jìn)行擴(kuò)容,如果兩個(gè)元素同時(shí)添加,線程和線程可能同時(shí)擴(kuò)容。

1.HashMap結(jié)構(gòu)

????HashMap是存鍵值對(duì)(key-value)映射的數(shù)據(jù)結(jié)構(gòu),由數(shù)組+鏈表組成的,數(shù)組是HashMap的主體,鏈表則是主要為了解決哈希沖突而存在的,如果定位到的數(shù)組位置不含鏈表(當(dāng)前entry的next指向null),那么對(duì)于查找,添加等操作很快,僅需一次尋址即可;如果定位到的數(shù)組包含鏈表,對(duì)于添加操作,其時(shí)間復(fù)雜度為O(n),首先遍歷鏈表,存在即覆蓋,否則新增;對(duì)于查找操作來(lái)講,仍需遍歷鏈表,然后通過(guò)key對(duì)象的equals方法逐一比對(duì)查找。通過(guò)下圖可以對(duì)HashMap有一個(gè)直觀的認(rèn)識(shí)。

2.put和get操作過(guò)程

put操作(JDK1.8版)

????1)計(jì)算key的hash值(計(jì)算過(guò)程下面會(huì)詳細(xì)介紹)。

????2)如果數(shù)組table是否為null或長(zhǎng)度是否等于0,條件為true時(shí)進(jìn)行數(shù)據(jù)table擴(kuò)容(實(shí)際執(zhí)行resize方法)。

????3)根據(jù)hash值計(jì)算Value將要存放的位置,即計(jì)算數(shù)組table索引(計(jì)算過(guò)程下面會(huì)詳細(xì)講)。

????4)判斷table[i]是否是null,如果是null直接進(jìn)行Value插入。

????5)如果table[i]不是null,接著判斷key是否重復(fù),如果重復(fù)直接進(jìn)行覆蓋插入。

????6)如果key不重復(fù),判斷table[i]是否是TreeNode類(lèi)型,如果是紅黑樹(shù),直接插入。

????7)如果不是TreeNode就遍歷鏈表,遍歷時(shí)預(yù)判斷插入新的Value后,鏈表長(zhǎng)度是否大于等于8。條件為T(mén)rue時(shí)執(zhí)行鏈表轉(zhuǎn)紅黑樹(shù),然后插入Value。

????8)如果上面鏈表長(zhǎng)度小于8執(zhí)行鏈表插入。

????9)最后檢查數(shù)組是否需要擴(kuò)容。

詳細(xì)代碼:

    public V put(K key, V value) {
        //調(diào)用putVal方法
        return putVal(hash(key), key, value, false, true);
    }

    final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
        Node[] tab;  //緩存底層數(shù)組用,都是指向一個(gè)地址的引用
        Node p;      //插入數(shù)組的桶i處的鍵值對(duì)節(jié)點(diǎn)
        int n;            //底層數(shù)組的長(zhǎng)度
        int i;            //插入數(shù)組的桶的下標(biāo)
        //剛開(kāi)始table是null或空的時(shí)候,初始化個(gè)默認(rèn)的table;為tab和n賦值,tab指向底層數(shù)組,n為底層數(shù)組的長(zhǎng)度
        if ((tab = table) == null || (n = tab.length) == 0){
            n = (tab = resize()).length;
        }
        //(n - 1) & hash:根據(jù)hash值算出插入點(diǎn)在底層數(shù)組的桶的位置,即下標(biāo)值;為p賦值,也為i賦值(不管碰撞與否,都已經(jīng)賦值了)
        //如果在數(shù)組上,沒(méi)有發(fā)生碰撞,即當(dāng)前要插入的位置上之前沒(méi)有插入過(guò)值,則直接在此位置插入要插入的鍵值對(duì)
        if ((p = tab[i = (n - 1) & hash]) == null){
            tab[i] = newNode(hash, key, value, null);//插入的節(jié)點(diǎn)的next屬性是null
        } else {    //發(fā)生碰撞,即當(dāng)前位置已經(jīng)插入了值
            Node e;    //中間變量吧,跟冒泡排序里面的那個(gè)中間變量似的,起到個(gè)值交換的作用
            K k;            //同上
            //hash值相同,key也相同,那么就是更新這個(gè)鍵值對(duì)的值。同 jdk 1.7
            if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))){ //注意在這個(gè)if內(nèi)【e != null】
                e = p;//這地方,e = p  他們兩個(gè)都是指向數(shù)組下標(biāo)為i的地方,在這if else if else結(jié)束之后,再把節(jié)點(diǎn)的值value給更新了
            } else if (p instanceof TreeNode){  //這個(gè)樹(shù)方法可能會(huì)返回null。
                //jdk 1.8引入了紅黑樹(shù)來(lái)處理碰撞,上面判斷p的類(lèi)型已經(jīng)是樹(shù)結(jié)構(gòu)了,
                e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value);//如果是,則走添加樹(shù)的方法。
            } else {    //注意在這個(gè)else內(nèi),當(dāng)為添加新節(jié)點(diǎn)時(shí),【e == 】;更新某個(gè)節(jié)點(diǎn)時(shí),就不是null
                for (int binCount = 0; ; ++binCount) {//還未形成樹(shù)結(jié)構(gòu),還是jdk 1.7的鏈表結(jié)構(gòu)
                    //差別就是1.7:是頭插法,后來(lái)的留在數(shù)組上,先來(lái)的鏈在尾上;1.8:是先來(lái)的就留在數(shù)組上,后來(lái)的鏈在尾上
                    //判斷p.next是否為空,同時(shí)為e賦值,若為空,則p.next指向新添加的節(jié)點(diǎn),這是在鏈表長(zhǎng)度小于7的時(shí)候
                    if ((e = p.next) == null) {
                        //這個(gè)地方有個(gè)不好理解的地方:在判斷條件里面,把e指向p.next,也就是說(shuō)現(xiàn)在e=null而不是下下一行錯(cuò)誤的理解。
                        //這也就解釋了更新的時(shí)候,返回oldValue,新建的時(shí)候,是不在那地方返回的。
                        p.next = newNode(hash, key, value, null);//e = p.next,p.next指向新生成的節(jié)點(diǎn),也就是說(shuō)e指向新節(jié)點(diǎn)(錯(cuò)誤)
                        //對(duì)于臨界值的分析:
                        //假設(shè)此次是第六次,binCount == 6,不會(huì)進(jìn)行樹(shù)變,當(dāng)前鏈表長(zhǎng)度是7;下次循環(huán)。
                        //binCount == 7,條件成立,進(jìn)行樹(shù)變,以后再put到這個(gè)桶的位置的時(shí)候,這個(gè)else就不走了,走中間的那個(gè)數(shù)結(jié)構(gòu)的分叉語(yǔ)句啦
                        //這個(gè)時(shí)候,長(zhǎng)度為8的鏈表就變成了紅黑樹(shù)啦
                        if (binCount >= TREEIFY_THRESHOLD - 1){// -1 for 1st //TREEIFY_THRESHOLD == 8
                            treeifyBin(tab, hash);
                        }
                        break;//插入新值或進(jìn)行樹(shù)變后,跳出for循環(huán)。此時(shí)e未重定向,還是指向null,雖然后面p.next指向了新節(jié)點(diǎn)。
                        //但是,跟e沒(méi)關(guān)系。
                    }
                    //如果在循環(huán)鏈表的時(shí)候,找到key相同的節(jié)點(diǎn),那么就跳出循環(huán),就走不到鏈表的尾上了。
                    // e已經(jīng)在上一步已經(jīng)賦值了,且不為null,也會(huì)跳出for循環(huán),會(huì)在下面更新value的值
                    if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))){
                        break;
                    }
                    //這個(gè)就是p.next也就是e不為空,然后,還沒(méi)有key相同的情況出現(xiàn),那就繼續(xù)循環(huán)鏈表,
                    // p指向p.next也就是e,繼續(xù)循環(huán),繼續(xù),e=p.next
                    p = e;
                    //直到p.next為空,添加新的節(jié)點(diǎn);或者出現(xiàn)key相等,更新舊值的情況才跳出循環(huán)。
                }
            }
            //經(jīng)過(guò)上面if else if else之后,e在新建節(jié)點(diǎn)的時(shí)候,為null;更新的時(shí)候,則被賦值。在樹(shù)里面處理putTreeVal()同樣如此,
            if (e != null) { // existing mapping for key//老外說(shuō)的對(duì),就是只有更新的時(shí)候,才走這,才會(huì)直接return oldValue
                V oldValue = e.value;
                //onlyIfAbsent 這個(gè)在調(diào)用hashMap的put()的時(shí)候,一直是false,那么下面更新value是肯定執(zhí)行的
                if (!onlyIfAbsent || oldValue == null){
                    e.value = value;
                }
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold){
            resize();
        }
        afterNodeInsertion(evict);
        return null;
3.尋址過(guò)程(怎么由key值轉(zhuǎn)換成數(shù)組下標(biāo))

借用網(wǎng)上的圖,先比較直觀的看下這個(gè)轉(zhuǎn)換過(guò)程:

????第一步:key值--->32位的哈希值
????這個(gè)就是key值調(diào)用hashCode()函數(shù),生成一個(gè)32位的哈希值。
????第二步:32位哈希值--->混合哈希值
????這一步將32位哈希值的高16位與低16位做異或操作。為什么要這樣做呢,因?yàn)楹竺孢€要進(jìn)行一次indexFoe()操作截取低位信息(高位信息將會(huì)丟失),因此高低位異或操作后,低16位也能摻雜高16位的信息,高位的信息變相被保存下來(lái)了,可以增加隨機(jī)性,減小沖突的可能性。
1,2兩步的操作在put函數(shù)源碼中合并成了一行代碼,如下:

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

????第三步:混合哈希值--->數(shù)組下標(biāo)
鏈表數(shù)組的初始長(zhǎng)度是16。顯然這個(gè)32位的混合哈希值不能直接和鏈表數(shù)組直接對(duì)應(yīng)起來(lái),會(huì)照成大量沖突。這里采用了一次取模運(yùn)算。HashMap 通過(guò) hash 值與 length-1 (容器長(zhǎng)度-1)進(jìn)行取模(%)運(yùn)算。可能有人會(huì)問(wèn):明明源碼中 indexFor() 方法進(jìn)行的 按位與(&)運(yùn)算,而非取模運(yùn)算。實(shí)際上,HashMap 中的 indexFor() 方法就是在進(jìn)行取模運(yùn)算。利用位運(yùn)算代替取模運(yùn)算,可以大大提高程序的計(jì)算效率。位運(yùn)算可以直接對(duì)內(nèi)存數(shù)據(jù)進(jìn)行操作,不需要轉(zhuǎn)換成十進(jìn)制,因此效率要高得多。需要注意的是,只有在特定情況下,位運(yùn)算才可以轉(zhuǎn)換成取模運(yùn)算(當(dāng) b = 2^n 時(shí),a % b = a & (b - 1) )。也是因此,HashMap 才將初始長(zhǎng)度設(shè)置為 16,且擴(kuò)容只能是以 2 的倍數(shù)(2^n)擴(kuò)容。

4.HashMap的安全性

在擴(kuò)容操作時(shí)可能形成環(huán)形鏈表。
原因:
在HashMap擴(kuò)容時(shí),會(huì)改變鏈表中的元素的順序,將元素從鏈表頭部插入。(為了避免尾部遍歷)。而環(huán)形鏈表就在這一時(shí)刻發(fā)生。添加元素時(shí),如果超過(guò)閾值,就要進(jìn)行擴(kuò)容,如果兩個(gè)元素同時(shí)添加,線程A和線程B可能同時(shí)擴(kuò)容。線程A準(zhǔn)備擴(kuò)容時(shí),線程B開(kāi)始執(zhí)行,擴(kuò)容完畢,產(chǎn)生新的hashMap. 此時(shí)A—>B?null變成B—>A ?null,先將A復(fù)制到新的hash表中,然后接著復(fù)制B到鏈頭(A的前邊:B.next=A),本來(lái)B.next=null,到此也就結(jié)束了(跟線程二一樣的過(guò)程),但是,由于線程二擴(kuò)容的原因,將B.next=A,所以,這里繼續(xù)復(fù)制A,讓A.next=B,由此,環(huán)形鏈表出現(xiàn):B.next=A; A.next=B。本來(lái)應(yīng)該通過(guò) A.next=B來(lái)讓A指向null 的,但是線程A已經(jīng)把A.next變成B的位置了。所有行成回環(huán)。

5.HashSet

因?yàn)閔ashSet底層也是利用hashMap實(shí)現(xiàn)的,所以這里一起分析下。
HashSet 是一個(gè)沒(méi)有重復(fù)元素的集合,它是由HashMap實(shí)現(xiàn)的,不保證元素的順序,而且HashSet允許使用 null 元素。
HashSet是非同步的。如果多個(gè)線程同時(shí)訪問(wèn)一個(gè)哈希 set,而其中至少一個(gè)線程修改了該 set,那么它必須 保持外部同步。這通常是通過(guò)對(duì)自然封裝該 set 的對(duì)象執(zhí)行同步操作來(lái)完成的。如果不存在這樣的對(duì)象,則應(yīng)該使用 Collections.synchronizedSet 方法來(lái)“包裝” set。最好在創(chuàng)建時(shí)完成這一操作,以防止對(duì)該 set 進(jìn)行意外的不同步訪問(wèn):Set s = Collections.synchronizedSet(new HashSet(...));

HashSet偽代碼實(shí)現(xiàn):

public class MyHashSet {
    private HashMap map;
    private static final Object PRESENT = new Object();
    public MyHashSet(){
        map = new HashMap();
    }
    public int size(){
        return map.size();
    }
    public void add(E e){
        map.put(e,PRESENT);
    }
    public void remove(E e){
        map.remove(e);
    }
}

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/75508.html

相關(guān)文章

  • Java相關(guān)

    摘要:本文是作者自己對(duì)中線程的狀態(tài)線程間協(xié)作相關(guān)使用的理解與總結(jié),不對(duì)之處,望指出,共勉。當(dāng)中的的數(shù)目而不是已占用的位置數(shù)大于集合番一文通版集合番一文通版垃圾回收機(jī)制講得很透徹,深入淺出。 一小時(shí)搞明白自定義注解 Annotation(注解)就是 Java 提供了一種元程序中的元素關(guān)聯(lián)任何信息和著任何元數(shù)據(jù)(metadata)的途徑和方法。Annotion(注解) 是一個(gè)接口,程序可以通過(guò)...

    wangtdgoodluck 評(píng)論0 收藏0
  • 3分鐘搞掂Set集合

    摘要:下面總結(jié)一下集合常用的三個(gè)子類(lèi)吧無(wú)序,允許為,底層是散列表紅黑樹(shù),非線程同步有序,不允許為,底層是紅黑樹(shù)非線程同步迭代有序,允許為,底層是雙向鏈表,非線程同步從結(jié)論而言我們就可以根據(jù)自己的實(shí)際情況來(lái)使用了。 前言 聲明,本文用的是jdk1.8 前面章節(jié)回顧: Collection總覽 List集合就這么簡(jiǎn)單【源碼剖析】 Map集合、散列表、紅黑樹(shù)介紹 HashMap就是這么簡(jiǎn)單【源碼...

    widuu 評(píng)論0 收藏0
  • 一份送給Java初學(xué)者的指南

    摘要:編程思想第版這本書(shū)要常讀,初學(xué)者可以快速概覽,中等程序員可以深入看看,老鳥(niǎo)還可以用之回顧的體系。以下視頻整理自慕課網(wǎng)工程師路徑相關(guān)免費(fèi)課程。 我自己總結(jié)的Java學(xué)習(xí)的系統(tǒng)知識(shí)點(diǎn)以及面試問(wèn)題,目前已經(jīng)開(kāi)源,會(huì)一直完善下去,歡迎建議和指導(dǎo)歡迎Star: https://github.com/Snailclimb/Java-Guide 筆者建議初學(xué)者學(xué)習(xí)Java的方式:看書(shū)+視頻+實(shí)踐(初...

    banana_pi 評(píng)論0 收藏0
  • Java集合總結(jié)【面試題+腦圖】,將知識(shí)點(diǎn)一網(wǎng)打盡!

    摘要:而在集合中,值僅僅是一個(gè)對(duì)象罷了該對(duì)象對(duì)本身而言是無(wú)用的。將這篇文章作為集合的總結(jié)篇,但覺(jué)得沒(méi)什么好寫(xiě)就回答一些面試題去了,找了一會(huì)面試題又覺(jué)得不夠系統(tǒng)。 前言 聲明,本文用的是jdk1.8 花了一個(gè)星期,把Java容器核心的知識(shí)過(guò)了一遍,感覺(jué)集合已經(jīng)無(wú)所畏懼了??!(哈哈哈....),現(xiàn)在來(lái)總結(jié)一下吧~~ 回顧目錄: Collection總覽 List集合就這么簡(jiǎn)單【源碼剖析】 Ma...

    yearsj 評(píng)論0 收藏0
  • Java編程基礎(chǔ)19——Map集合&斗地主案例

    摘要:使用默認(rèn)隨機(jī)源對(duì)指定列表進(jìn)行置換。將集合排序使用二分搜索法搜索指定列表,以獲得指定對(duì)象根據(jù)元素的自然順序,返回給定的最大元素。 1_Map集合概述和特點(diǎn) A:Map接口概述 查看API可以知道: 將鍵映射到值的對(duì)象 一個(gè)映射不能包含重復(fù)的鍵 每個(gè)鍵最多只能映射到一個(gè)值 B:Map接口和Collection接口的不同 Map是雙列的,Collection是單列的 Map...

    ygyooo 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<