成人无码视频,亚洲精品久久久久av无码,午夜精品久久久久久毛片,亚洲 中文字幕 日韩 无码

資訊專欄INFORMATION COLUMN

集合源碼學(xué)習(xí)之路---hashMap(jdk1.8)

kamushin233 / 1164人閱讀

摘要:值得位數(shù)有的次方,如果直接拿散列值作為下標(biāo)訪問(wèn)主數(shù)組的話,只要算法比較均勻,一般是很難出現(xiàn)碰撞的。但是內(nèi)存裝不下這么大的數(shù)組,所以計(jì)算數(shù)組下標(biāo)就采取了一種折中的辦法,就是將得到的散列值與數(shù)組長(zhǎng)度做一個(gè)與操作。

hashMap簡(jiǎn)單介紹

hashMap是面試中的高頻考點(diǎn),或許日常工作中我們只需把hashMap給new出來(lái),調(diào)用put和get方法就完了。但是hashMap給我們提供了一個(gè)絕佳的范例,展示了編程中對(duì)數(shù)據(jù)結(jié)構(gòu)和算法的應(yīng)用,例如位運(yùn)算、hash,數(shù)組,鏈表、紅黑樹等,學(xué)習(xí)hashMap絕對(duì)是有好處的。
廢話不多說(shuō),要想學(xué)習(xí)hashMap,必先明白其數(shù)據(jù)結(jié)構(gòu)。在java中,最基礎(chǔ)的數(shù)據(jù)結(jié)構(gòu)就兩種,一種是數(shù)組,另外一個(gè)就是模擬指針(引用),一起來(lái)看下hashMap結(jié)構(gòu)圖:

類定義

從類定義上看,繼承于AbstractMap,并實(shí)現(xiàn)Map接口,其實(shí)就是里面定義了一些常用方法比如size(),isEmpty(),containsKey(),get(),put()等等,Cloneable,Serializable 的作用在之前l(fā)ist章節(jié)已講述過(guò)就不再重復(fù)了,整體來(lái)說(shuō)類定義還是蠻簡(jiǎn)單的

public class HashMap extends AbstractMap
    implements Map, Cloneable, Serializable 
源碼分析

接下來(lái)會(huì)帶領(lǐng)大家閱讀源碼,有些不重要的,會(huì)咔掉一部分。

//初始容量16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
//最大容量2的30次方
static final int MAXIMUM_CAPACITY = 1 << 30;
//默認(rèn)加載因子,用來(lái)計(jì)算threshold
static final float DEFAULT_LOAD_FACTOR = 0.75f;
//鏈表轉(zhuǎn)成樹的閾值,當(dāng)桶中鏈表長(zhǎng)度大于8時(shí)轉(zhuǎn)成樹
static final int TREEIFY_THRESHOLD = 8;
//進(jìn)行resize操作室,若桶中數(shù)量少于6則從樹轉(zhuǎn)成鏈表
static final int UNTREEIFY_THRESHOLD = 6;
//當(dāng)桶中的bin樹化的時(shí)候,最小hashtable容量,最少是TREEIFY_THRESHOLD 的4倍
static final int MIN_TREEIFY_CAPACITY = 64;
//在樹化之前,桶中的單個(gè)bin都是node,實(shí)現(xiàn)了Entry接口
 static class Node implements Map.Entry {
        final int hash;
        final K key;
        V value;
        Node next;

        Node(int hash, K key, V value, Node next) {
            this.hash = hash;
            this.key = key;
            this.value = value;
            this.next = next;
        }

        public final int hashCode() {
            return Objects.hashCode(key) ^ Objects.hashCode(value);
        }
    }
為什么hashMap容量總是2的冪次方?
//jdk1.8
static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
    

hashMap中的hash算法,影響hashMap效率的重要因素之一就是hash算法的好壞。hash算法的好壞,我們可以簡(jiǎn)單的通過(guò)兩個(gè)因素判斷,1是否高效2是否均勻。
大家都知道key.hashCode調(diào)用的是key鍵值類型自帶的哈希函數(shù),返回int散列值。int值得位數(shù)有2的32次方,如果直接拿散列值作為下標(biāo)訪問(wèn)hashMap主數(shù)組的話,只要hash算法比較均勻,一般是很難出現(xiàn)碰撞的。但是內(nèi)存裝不下這么大的數(shù)組,所以計(jì)算數(shù)組下標(biāo)就采取了一種折中的辦法,就是將hash()得到的散列值與數(shù)組長(zhǎng)度做一個(gè)與操作。如下函數(shù):

//或許大家會(huì)發(fā)現(xiàn)這個(gè)方法是jdk1.7,為什么不用1.8的呢?那是因?yàn)?.8里已經(jīng)去掉這個(gè)函數(shù),直接調(diào)用,為了講解方便,我從1.7中找出此方法方便學(xué)習(xí)
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);
    }

hashMap的長(zhǎng)度必須是2的冪次方,最小為16.順便說(shuō)一下這樣設(shè)計(jì)的好處。因?yàn)檫@樣(length-1)正好是一個(gè)高位掩碼,&運(yùn)算會(huì)將高位置0,只保留低位數(shù)字。我們來(lái)舉個(gè)例子,假設(shè)長(zhǎng)度為16,16-1=15,15的2進(jìn)制表示為
00000000 00000000 00000000 00001111.隨意定義一個(gè)散列值做&運(yùn)算,結(jié)果如所示:

    10101010 11110011 00111010 01011010
  & 00000000 00000000 00000000 00001111
  -------------------------------------
    00000000 00000000 00000000 00001010 

也就是說(shuō)實(shí)際上只截取了最低的四位,也就是我們計(jì)算的索引結(jié)果。但是只取后幾位的話,就算散列值分布再均勻,hash碰撞也會(huì)很嚴(yán)重,如果hashcode函數(shù)本身不好,分布上成等差數(shù)列的漏洞,使最后幾個(gè)低位成規(guī)律性重復(fù),這就無(wú)比蛋疼了。這時(shí)候hash()函數(shù)的價(jià)值就體現(xiàn)出來(lái)了

h=key.hashcode()    11111011 01011111 00011100 01011011
        h>>>16    ^ 00000000 00000000 11111011 01011111
                  -------------------------------------
                    11111011 01011111 11100111 00000100
                  & 00000000 00000000 00000000 00001111
                  -------------------------------------
                    00000000 00000000 00000000 00000100

(h = key.hashCode()) ^ (h >>> 16),16正好是32的一半,其目的是為了將自己的高半?yún)^(qū)和低半?yún)^(qū)做異或,混合高低位信息,以此來(lái)加大低位的隨機(jī)性,而且混合后的低位存在部分高位的特征,算是變相的保留了高位信息。由此看來(lái)jdk1.8對(duì)于hash算法和計(jì)算索引值的設(shè)計(jì)就基本暴露在我們的眼前了,不得不佩服設(shè)計(jì)之巧妙。

//返回大于等于cap且距離最近的一個(gè)2的冪   
//例子:cap=2 return 4;  cap=9 return 16;
static final int tableSizeFor(int cap) {
        int n = cap - 1;
        n |= n >>> 1;
        n |= n >>> 2;
        n |= n >>> 4;
        n |= n >>> 8;
        n |= n >>> 16;
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }
//hashMap數(shù)組,保留著鏈表的頭結(jié)點(diǎn)或者紅黑樹的跟結(jié)點(diǎn)。
//當(dāng)?shù)谝淮问褂玫臅r(shí)候,會(huì)對(duì)其初始化,當(dāng)需要擴(kuò)容時(shí),會(huì)調(diào)用resize方法。長(zhǎng)度一定是2的冪
transient Node[] table;
//用來(lái)遍歷的set集合,速度快于keySet
transient Set> entrySet;

transient int size;
//用來(lái)檢測(cè)使用iterator期間結(jié)構(gòu)是否發(fā)生變化,變化則觸發(fā)fail-fast機(jī)制
transient int modCount;
//當(dāng)容器內(nèi)映射數(shù)量達(dá)到時(shí),發(fā)生resize操作(threshold=capacity * load factor)
int threshold;
//加載因子,默認(rèn)0.75
final float loadFactor;
get方法解析
public V get(Object key) {
        Node e;
        return (e = getNode(hash(key), key)) == null ? null : e.value;
    }
    
final Node getNode(int hash, Object key) {
        Node[] tab; Node first, e; int n; K k;
        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;
            if ((e = first.next) != null) {
                if (first instanceof TreeNode)
                    return ((TreeNode)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;
    }

hash函數(shù)之前已經(jīng)研究過(guò)了,直接鎖定getNode()吧。通過(guò)hash函數(shù)算出hash值&上數(shù)組長(zhǎng)度從而計(jì)算出索引值,然后遍歷比較key,返回對(duì)應(yīng)值。

put和resize方法解析
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[] tab; Node p; int n, i;
        //若table為空,則調(diào)用resize()進(jìn)行初始化,并將長(zhǎng)度賦值給n
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        //根據(jù)(n-1)&hash算出索引,得到結(jié)點(diǎn)p,若p為null,則生成一個(gè)新的結(jié)點(diǎn)插入。
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
        //若p不為null,則將p和插入結(jié)點(diǎn)的key與其hash值進(jìn)行比較,若相同將p的引用同時(shí)賦值給e
            Node e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            //若不同,且結(jié)點(diǎn)p屬于樹節(jié)點(diǎn),則調(diào)用putTreeVal()
            else if (p instanceof TreeNode)
                e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value);
            else {
            //若不同,則將p當(dāng)做鏈表的頭結(jié)點(diǎn),循環(huán)比較,若為null則新增節(jié)點(diǎn),且循環(huán)次數(shù)大于等于TREEIFY_THRESHOLD - 1則從鏈表結(jié)構(gòu)轉(zhuǎn)為樹結(jié)構(gòu)
                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;
                    }
                    //若鏈表中其中一結(jié)點(diǎn)的key與hash與插入結(jié)點(diǎn)一致,則跳出循環(huán)
                    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;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        //若插入的key在map中不存在,則判斷size>thresold
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }
    //初始化數(shù)組和擴(kuò)容使用
    final Node[] resize() {
        Node[] 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[] newTab = (Node[])new Node[newCap];
        table = newTab;
        //若原數(shù)組不為空,則對(duì)其中元素進(jìn)行重新排列
        if (oldTab != null) {
            for (int j = 0; j < oldCap; ++j) {
                Node 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)e).split(this, newTab, j, oldCap);
                    else { // preserve order
                        Node loHead = null, loTail = null;
                        Node hiHead = null, hiTail = null;
                        Node 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;
    }
entrySet和keySet

hashMap中遍歷的方式是通過(guò)entrySet和keySet,為了保證其效率,建議用entrySet因?yàn)樗拇鎯?chǔ)結(jié)構(gòu)和hashMap一致。hashMap是如何維護(hù)entrySet的呢?通過(guò)閱讀源碼,發(fā)現(xiàn)在put的時(shí)候,并沒(méi)有對(duì)entrySet進(jìn)行維護(hù),且源碼中
entrySet方法只是new了個(gè)對(duì)象,那這個(gè)entrySet視圖的數(shù)據(jù)從哪而來(lái)呢?

public Set> entrySet() {
        Set> es;
        return (es = entrySet) == null ? (entrySet = new EntrySet()) : es;
    }

    final class EntrySet extends AbstractSet> {
        public final int size()                 { return size; }
        public final void clear()               { HashMap.this.clear(); }
        public final Iterator> iterator() {
            return new EntryIterator();
        } 
    }
    
    final class EntryIterator extends HashIterator
        implements Iterator> {
        public final Map.Entry next() { return nextNode(); }
    }
    
    abstract class HashIterator {
        Node next;        // next entry to return
        Node current;     // current entry
        int expectedModCount;  // for fast-fail
        int index;             // current slot

        HashIterator() {
            expectedModCount = modCount;
            Node[] t = table;
            current = next = null;
            index = 0;
            if (t != null && size > 0) { // advance to first entry
                do {} while (index < t.length && (next = t[index++]) == null);
            }
        }

        final Node nextNode() {
            Node[] t;
            Node e = next;
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            if (e == null)
                throw new NoSuchElementException();
            if ((next = (current = e).next) == null && (t = table) != null) {
                do {} while (index < t.length && (next = t[index++]) == null);
            }
            return e;
        }
    }

通過(guò)閱讀EntrySet,我們發(fā)現(xiàn)其iterator() 調(diào)用了EntryIterator(),而在對(duì)其進(jìn)行實(shí)例化的時(shí)候會(huì)對(duì)其父類HashIterator進(jìn)行實(shí)例化,從HashIterator的構(gòu)造方法和nextNode我們發(fā)現(xiàn),其返回的視圖就是作用于table的,所以無(wú)需重新開辟內(nèi)存。

總結(jié)

本篇文章主要分析hashMap的存儲(chǔ)結(jié)構(gòu),分析了hashMap為什么容量始終是2的冪,分析了其hash算法的好壞和影響其效率的因素,同時(shí)也了解到了在put和get時(shí)做了哪些操作和其中數(shù)據(jù)結(jié)構(gòu)的變化。最后通過(guò)hashMap常見(jiàn)的遍歷方式,得出entrySet是便利效率最高的,且hashMap維護(hù)entrySet的方式。通過(guò)學(xué)習(xí),發(fā)現(xiàn)hashMap的設(shè)計(jì)非常優(yōu)秀,但無(wú)奈能力有限,無(wú)法將其精妙之處全部剖析開來(lái)。
下節(jié)預(yù)告:分析一下并發(fā)下的hashMap有可能造成的閉環(huán)問(wèn)題和concurrentHashMap

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

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

相關(guān)文章

  • java源碼

    摘要:集合源碼解析回歸基礎(chǔ),集合源碼解析系列,持續(xù)更新和源碼分析與是兩個(gè)常用的操作字符串的類。這里我們從源碼看下不同狀態(tài)都是怎么處理的。 Java 集合深入理解:ArrayList 回歸基礎(chǔ),Java 集合深入理解系列,持續(xù)更新~ JVM 源碼分析之 System.currentTimeMillis 及 nanoTime 原理詳解 JVM 源碼分析之 System.currentTimeMi...

    Freeman 評(píng)論0 收藏0
  • 集合框架源碼學(xué)習(xí)HashMap(JDK1.8)

    摘要:所謂拉鏈法就是將鏈表和數(shù)組相結(jié)合。若遇到哈希沖突,則將沖突的值加到鏈表中即可。在編寫程序中,要盡量避免。 目錄: 0-1. 簡(jiǎn)介 0-2. 內(nèi)部結(jié)構(gòu)分析   0-2-1. JDK18之前   0-2-2. JDK18之后 0-3. LinkedList源碼分析   0-3-1. 構(gòu)造方法   0-3-2. put方法   0-3-3. get方法   0-3-4. resize方法 ...

    yangrd 評(píng)論0 收藏0
  • 這幾道Java集合框架面試題在面試中幾乎必問(wèn)

    摘要:若遇到哈希沖突,則將沖突的值加到鏈表中即可。之后相比于之前的版本,之后在解決哈希沖突時(shí)有了較大的變化,當(dāng)鏈表長(zhǎng)度大于閾值默認(rèn)為時(shí),將鏈表轉(zhuǎn)化為紅黑樹,以減少搜索時(shí)間。有序,唯一紅黑樹自平衡的排序二叉樹。 本文是最最最常見(jiàn)Java面試題總結(jié)系列第三周的文章。主要內(nèi)容: Arraylist 與 LinkedList 異同 ArrayList 與 Vector 區(qū)別 HashMap的底層...

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

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

    widuu 評(píng)論0 收藏0
  • ConcurrentHashMap基于JDK1.8源碼剖析

    摘要:下面我來(lái)簡(jiǎn)單總結(jié)一下的核心要點(diǎn)底層結(jié)構(gòu)是散列表數(shù)組鏈表紅黑樹,這一點(diǎn)和是一樣的。是將所有的方法進(jìn)行同步,效率低下。而作為一個(gè)高并發(fā)的容器,它是通過(guò)部分鎖定算法來(lái)進(jìn)行實(shí)現(xiàn)線程安全的。 前言 聲明,本文用的是jdk1.8 前面章節(jié)回顧: Collection總覽 List集合就這么簡(jiǎn)單【源碼剖析】 Map集合、散列表、紅黑樹介紹 HashMap就是這么簡(jiǎn)單【源碼剖析】 LinkedHas...

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

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

0條評(píng)論

kamushin233

|高級(jí)講師

TA的文章

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