jdk1.7HashMap

HashMap jkd1.7解讀

一.思路

我們都知道hashmap是一個(gè)查詢快速乔妈,的一個(gè)內(nèi)存容器,

設(shè)置思想:數(shù)組+鏈表

我用一個(gè)數(shù)組

[{1},{2},{3}]

  • 那么我如何隨機(jī)的讓一個(gè)map來(lái)分散的插入數(shù)組中氓皱,

    ? 我們需要用key來(lái)計(jì)算一個(gè)隨機(jī)數(shù):int code = key.hashcode(),這個(gè)我們都知道路召,那么這個(gè)生成的數(shù)字很大贮懈,我們想要生成一個(gè)索引的方法,很簡(jiǎn)單就是去摸:int index = code %entry[].length

    最后entry【index】 = enrye<key优训,value>

  • 接著我們來(lái)思考下一個(gè)問(wèn)題:如果數(shù)組對(duì)應(yīng)的索引上面有元素朵你,我們?cè)撛趺崔k?

    那么就是使用我們的鏈表

    • 什么是鏈表揣非,最簡(jiǎn)單的就是我的linkedlist抡医,我的對(duì)象了存儲(chǔ)了下一個(gè)節(jié)點(diǎn)的地址或者引用,那么hashmap中的entry對(duì)象就是這樣的一個(gè)對(duì)象早敬,一旦有新的值插入map忌傻,計(jì)算出的位置上已經(jīng)有元素了,那么就這樣的一個(gè)操作搞监,想將我們這個(gè)元素new entry()水孩,在放在table上table【index】= new entry,還要把已經(jīng)擁有的這個(gè)鏈表上的第一個(gè)元素的整體向下移動(dòng)
    • table[index] = new entry(index,code,table[i]=next)

二.put方法

 public V put(K key, V value) {
        if (table == EMPTY_TABLE) {
            inflateTable(threshold);
        }
        if (key == null)
            return putForNullKey(value);
        int hash = hash(key);
        int i = indexFor(hash, table.length);
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }

        modCount++;
        addEntry(hash, key, value, i);
        return null;
    }

1.inflateTable(threshold);初始話,初始化什么呢

  •     private void inflateTable(int toSize) {
            // Find a power of 2 >= toSize
            int capacity = roundUpToPowerOf2(toSize);
    
            threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
            table = new Entry[capacity];
            initHashSeedAsNeeded(capacity);
        }
    

Find a power of 2 >= toSize的意思是找打,一個(gè)比toSize大的2的冪次方的數(shù)

那么來(lái)看roundUpToPowerOf2(toSize);方法

  •     private static int roundUpToPowerOf2(int number) {
            // assert number >= 0 : "number must be non-negative";
            return number >= MAXIMUM_CAPACITY
                    ? MAXIMUM_CAPACITY
                    : (number > 1) ? Integer.highestOneBit((number - 1) << 1) : 1;
        }
    

這個(gè)方法最主要是看

Integer.highestOneBit((number - 1) << 1)

那么我們通過(guò)自己的測(cè)驗(yàn),可以得出highestOneBit(int i)方法時(shí)幫我們找到比i小的二的冪次方的數(shù),那么如何做的呢,繼續(xù)往下看

    public static int highestOneBit(int i) {
        // HD, Figure 3-1
        i |= (i >>  1);
        i |= (i >>  2);
        i |= (i >>  4);
        i |= (i >>  8);
        i |= (i >> 16);
        return i - (i >>> 1);
    }

我們看到了是通過(guò)位運(yùn)算來(lái)計(jì)算的,然后通過(guò)方法名字我們大概可以知道,這個(gè)方法時(shí)拿到我們這個(gè)數(shù)字的最高位是一對(duì)應(yīng)的那個(gè)數(shù)字,重點(diǎn)來(lái)了,最高位為1,其他為0(負(fù)數(shù)除外),這個(gè)數(shù)字正好是二的冪次方,這個(gè)算法的思路大概就是把最高位的數(shù)字1,慢慢的向右移,然后在或運(yùn)算,

  • i |= (i >> 1);這個(gè)方法10:0000 1010,想右移一位 0000 0101,在做運(yùn)算,0000 1111 ,看這個(gè)就把高位的1(因?yàn)樽罡呶坏囊欢ㄊ且粋€(gè)1),然后使得最高位的后一位保證變?yōu)?,
  • 因?yàn)槲覀冎乐纈nt是32位的數(shù)字,所有需要繼續(xù)移動(dòng),那么最高位是1,后一位也是1,所有第二部是在替換后面的2位為1,以此類推
  • 最后沒(méi)一定可以得到一個(gè)從源數(shù)據(jù)的的最高位開(kāi)始后面全部為1的數(shù)據(jù),那么將這個(gè)數(shù)向后移動(dòng)一位,我們一定可以得到一個(gè)除最高位為1外,其他為全部為0的而精致數(shù)(裝換過(guò)來(lái),也就是一個(gè)二的冪次方的數(shù))

那么我們現(xiàn)在需要的是講個(gè)數(shù)字變大,但是又不能打過(guò)下個(gè)2的冪次方的數(shù),及8<10<16,那么我們的變大需要變成的這個(gè)x的范圍是 16<x<32,所有我們需要向左移動(dòng)一位,這個(gè)就可以得到了.

Integer.highestOneBit((number - 1) << 1)

至于這個(gè)-1是用來(lái)處理特殊數(shù)據(jù)的,比如說(shuō)8,16這些

private void inflateTable(int toSize) {
    // Find a power of 2 >= toSize
    int capacity = roundUpToPowerOf2(toSize);

    threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
    table = new Entry[capacity];
    initHashSeedAsNeeded(capacity);
}

在回頭來(lái)看這個(gè)方法,我們的capacity是那倒一個(gè)二的冪次的數(shù)組琐驴,

現(xiàn)在的第二個(gè)問(wèn)題俘种??绝淡?宙刘?

為什么是比這個(gè)數(shù)字大的二的冪次方的數(shù)字(碰撞數(shù)小牢酵?悬包?)

繼續(xù)看,拿到這個(gè)數(shù)字是想做什么馍乙,算出一個(gè)hashmap的最大容量書和這個(gè)數(shù)字與擴(kuò)容因子(loadFactor)的積

布近,其實(shí)就是這個(gè)數(shù)字,去付給這個(gè)Entry[]數(shù)組的初始化大小丝格,然后進(jìn)入了initHashSeedAsNeeded(capacity)方法撑瞧,我們?cè)賮?lái)看

    final boolean initHashSeedAsNeeded(int capacity) {
        boolean currentAltHashing = hashSeed != 0;
        boolean useAltHashing = sun.misc.VM.isBooted() &&
                (capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
        boolean switching = currentAltHashing ^ useAltHashing;
        if (switching) {
            hashSeed = useAltHashing
                ? sun.misc.Hashing.randomHashSeed(this)
                : 0;
        }
        return switching;
    }

currentAltHashing一般都是false,因?yàn)閔ashSeed(hash種子铁追?不懂)季蚂,本來(lái)就是0茫船,

useAltHashing:sun.misc.VM.isBooted()虛擬機(jī)是否啟動(dòng)琅束,應(yīng)該為true,那就看看后面這個(gè)判斷是什么

capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD

try {
                threshold = (null != altThreshold)
                        ? Integer.parseInt(altThreshold)
                        : ALTERNATIVE_HASHING_THRESHOLD_DEFAULT;

                // disable alternative hashing if -1
                if (threshold == -1) {
                    threshold = Integer.MAX_VALUE;
                }

                if (threshold < 0) {
                    throw new IllegalArgumentException("value must be positive integer.");
                }
            } catch(IllegalArgumentException failed) {
                throw new Error("Illegal value for 'jdk.map.althashing.threshold'", failed);
            }

            ALTERNATIVE_HASHING_THRESHOLD = threshold;

在這段代碼里算谈,我們知道了涩禀,

ALTERNATIVE_HASHING_THRESHOLD:這個(gè)就interger的最大的只,所以capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD這個(gè)為false然眼,

boolean switching = currentAltHashing ^ useAltHashing;這個(gè) 一個(gè)為false艾船,另一個(gè)也是false,,異或運(yùn)算屿岂,然后返回switching = false践宴,這里就是給這hashseed復(fù)制,也是hash算法的東西爷怀,到這里為止阻肩,我們算是吧一個(gè)Entry[]對(duì)象的初始化出來(lái)了,那么我們hashmap是:數(shù)組+鏈表

其中一部分出來(lái)了运授,但是到目前為止烤惊,還沒(méi)將我們傳入的key和value記錄下來(lái),接著看put方法

if (key == null)    return putForNullKey(value);
/**
將key為null的值方法數(shù)值的開(kāi)頭吁朦,由此我們自己可以知道柒室,這個(gè)地方只能存放一個(gè)值,并不能存放一個(gè)鏈表
*/    
private V putForNullKey(V value) {
        for (Entry<K,V> e = table[0]; e != null; e = e.next) {
            if (e.key == null) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
        modCount++;
        addEntry(0, null, value, 0);
        return null;
    }

接這個(gè)看下一個(gè)方法

int hash = hash(key);
final int hash(Object k) {
        int h = hashSeed;
        if (0 != h && k instanceof String) {
            return sun.misc.Hashing.stringHash32((String) k);
        }
        //獲取hashcode
        h ^= k.hashCode();

        //將高位的數(shù)字放下來(lái)逗宜,減少碰撞性
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }

//計(jì)算一個(gè)索引出來(lái)雄右,因?yàn)閿?shù)字是有長(zhǎng)度的,要根據(jù)hashcode去算計(jì)一個(gè)散列的索引到對(duì)應(yīng)的數(shù)字上去
int i = indexFor(hash, table.length);
static int indexFor(int h, int length) {
        // assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";
        return h & (length-1);
    }

//便利有沒(méi)有相同值纺讲,有的話做一個(gè)返回邏輯       
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }

最后一步不脯,將我們大key,value放到我們數(shù)組或者是鏈表上

    void addEntry(int hash, K key, V value, int bucketIndex) {
        //是否需要擴(kuò)容threshold = maxsize*loadFactor刻诊,
        if ((size >= threshold) && (null != table[bucketIndex])) {
            //擴(kuò)容的方法
            resize(2 * table.length);
            //根據(jù)新的數(shù)組重新hash防楷,重新計(jì)算數(shù)組
            hash = (null != key) ? hash(key) : 0;
            bucketIndex = indexFor(hash, table.length);
        }
        //這是添加元素entry對(duì)象
        createEntry(hash, key, value, bucketIndex);
    }

void resize(int newCapacity) {
        Entry[] oldTable = table;
        int oldCapacity = oldTable.length;
    //達(dá)到最大值就不擴(kuò)容了
        if (oldCapacity == MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return;
        }
        //根據(jù)當(dāng)前的大小重新創(chuàng)建數(shù)組
        Entry[] newTable = new Entry[newCapacity];
        //將舊 的方法復(fù)制到新的上面來(lái)
        transfer(newTable, initHashSeedAsNeeded(newCapacity));
        table = newTable;
        threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
    }

//上面我們講過(guò)了   boolean rehash為false
void transfer(Entry[] newTable, boolean rehash) {
        int newCapacity = newTable.length;
        for (Entry<K,V> e : table) {
            while(null != e) {
                Entry<K,V> next = e.next;
                //所以我們重新計(jì)算hash這一步,一般來(lái)說(shuō)是不會(huì)進(jìn)行的
                if (rehash) {
                    e.hash = null == e.key ? 0 : hash(e.key);
                }
                int i = indexFor(e.hash, newCapacity);
                e.next = newTable[i];
                newTable[i] = e;
                e = next;
            }
        }
    }

這個(gè)方法在單線程的情況下是沒(méi)有任何問(wèn)題则涯,但是在多線程的情況下就會(huì)出現(xiàn):死循環(huán)

到這里我們基本上已經(jīng)閱讀完成

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末复局,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子粟判,更是在濱河造成了極大的恐慌亿昏,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,427評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件档礁,死亡現(xiàn)場(chǎng)離奇詭異角钩,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)呻澜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門递礼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人羹幸,你說(shuō)我怎么就攤上這事脊髓。” “怎么了栅受?”我有些...
    開(kāi)封第一講書人閱讀 165,747評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵将硝,是天一觀的道長(zhǎng)恭朗。 經(jīng)常有香客問(wèn)我,道長(zhǎng)依疼,這世上最難降的妖魔是什么痰腮? 我笑而不...
    開(kāi)封第一講書人閱讀 58,939評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮律罢,結(jié)果婚禮上诽嘉,老公的妹妹穿的比我還像新娘。我一直安慰自己弟翘,他們只是感情好虫腋,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,955評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著稀余,像睡著了一般悦冀。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上睛琳,一...
    開(kāi)封第一講書人閱讀 51,737評(píng)論 1 305
  • 那天盒蟆,我揣著相機(jī)與錄音,去河邊找鬼师骗。 笑死历等,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的辟癌。 我是一名探鬼主播寒屯,決...
    沈念sama閱讀 40,448評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼黍少!你這毒婦竟也來(lái)了寡夹?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 39,352評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤厂置,失蹤者是張志新(化名)和其女友劉穎菩掏,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體昵济,經(jīng)...
    沈念sama閱讀 45,834評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡智绸,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,992評(píng)論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了访忿。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片瞧栗。...
    茶點(diǎn)故事閱讀 40,133評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖醉顽,靈堂內(nèi)的尸體忽然破棺而出沼溜,到底是詐尸還是另有隱情平挑,我是刑警寧澤游添,帶...
    沈念sama閱讀 35,815評(píng)論 5 346
  • 正文 年R本政府宣布系草,位于F島的核電站,受9級(jí)特大地震影響唆涝,放射性物質(zhì)發(fā)生泄漏找都。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,477評(píng)論 3 331
  • 文/蒙蒙 一廊酣、第九天 我趴在偏房一處隱蔽的房頂上張望能耻。 院中可真熱鬧,春花似錦亡驰、人聲如沸晓猛。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 32,022評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)戒职。三九已至,卻和暖如春透乾,著一層夾襖步出監(jiān)牢的瞬間洪燥,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,147評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工乳乌, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留捧韵,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,398評(píng)論 3 373
  • 正文 我出身青樓汉操,卻偏偏與公主長(zhǎng)得像再来,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子磷瘤,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,077評(píng)論 2 355