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

資訊專欄INFORMATION COLUMN

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

kamushin233 / 1090人閱讀

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

hashMap簡單介紹

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

類定義

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

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

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

//初始容量16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
//最大容量2的30次方
static final int MAXIMUM_CAPACITY = 1 << 30;
//默認(rèn)加載因子,用來計算threshold
static final float DEFAULT_LOAD_FACTOR = 0.75f;
//鏈表轉(zhuǎn)成樹的閾值,當(dāng)桶中鏈表長度大于8時轉(zhuǎn)成樹
static final int TREEIFY_THRESHOLD = 8;
//進行resize操作室,若桶中數(shù)量少于6則從樹轉(zhuǎn)成鏈表
static final int UNTREEIFY_THRESHOLD = 6;
//當(dāng)桶中的bin樹化的時候,最小hashtable容量,最少是TREEIFY_THRESHOLD 的4倍
static final int MIN_TREEIFY_CAPACITY = 64;
//在樹化之前,桶中的單個bin都是node,實現(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算法的好壞,我們可以簡單的通過兩個因素判斷,1是否高效2是否均勻。
大家都知道key.hashCode調(diào)用的是key鍵值類型自帶的哈希函數(shù),返回int散列值。int值得位數(shù)有2的32次方,如果直接拿散列值作為下標(biāo)訪問hashMap主數(shù)組的話,只要hash算法比較均勻,一般是很難出現(xiàn)碰撞的。但是內(nèi)存裝不下這么大的數(shù)組,所以計算數(shù)組下標(biāo)就采取了一種折中的辦法,就是將hash()得到的散列值與數(shù)組長度做一個與操作。如下函數(shù):

//或許大家會發(fā)現(xiàn)這個方法是jdk1.7,為什么不用1.8的呢?那是因為1.8里已經(jīng)去掉這個函數(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的長度必須是2的冪次方,最小為16.順便說一下這樣設(shè)計的好處。因為這樣(length-1)正好是一個高位掩碼,&運算會將高位置0,只保留低位數(shù)字。我們來舉個例子,假設(shè)長度為16,16-1=15,15的2進制表示為
00000000 00000000 00000000 00001111.隨意定義一個散列值做&運算,結(jié)果如所示:

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

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

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ū)做異或,混合高低位信息,以此來加大低位的隨機性,而且混合后的低位存在部分高位的特征,算是變相的保留了高位信息。由此看來jdk1.8對于hash算法和計算索引值的設(shè)計就基本暴露在我們的眼前了,不得不佩服設(shè)計之巧妙。

//返回大于等于cap且距離最近的一個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é)點或者紅黑樹的跟結(jié)點。
//當(dāng)?shù)谝淮问褂玫臅r候,會對其初始化,當(dāng)需要擴容時,會調(diào)用resize方法。長度一定是2的冪
transient Node[] table;
//用來遍歷的set集合,速度快于keySet
transient Set> entrySet;

transient int size;
//用來檢測使用iterator期間結(jié)構(gòu)是否發(fā)生變化,變化則觸發(fā)fail-fast機制
transient int modCount;
//當(dāng)容器內(nèi)映射數(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)研究過了,直接鎖定getNode()吧。通過hash函數(shù)算出hash值&上數(shù)組長度從而計算出索引值,然后遍歷比較key,返回對應(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()進行初始化,并將長度賦值給n
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        //根據(jù)(n-1)&hash算出索引,得到結(jié)點p,若p為null,則生成一個新的結(jié)點插入。
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
        //若p不為null,則將p和插入結(jié)點的key與其hash值進行比較,若相同將p的引用同時賦值給e
            Node e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            //若不同,且結(jié)點p屬于樹節(jié)點,則調(diào)用putTreeVal()
            else if (p instanceof TreeNode)
                e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value);
            else {
            //若不同,則將p當(dāng)做鏈表的頭結(jié)點,循環(huán)比較,若為null則新增節(jié)點,且循環(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é)點的key與hash與插入結(jié)點一致,則跳出循環(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ù)組和擴容使用
    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ù)組不為空,則對其中元素進行重新排列
        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中遍歷的方式是通過entrySet和keySet,為了保證其效率,建議用entrySet因為他的存儲結(jié)構(gòu)和hashMap一致。hashMap是如何維護entrySet的呢?通過閱讀源碼,發(fā)現(xiàn)在put的時候,并沒有對entrySet進行維護,且源碼中
entrySet方法只是new了個對象,那這個entrySet視圖的數(shù)據(jù)從哪而來呢?

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

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

總結(jié)

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

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

轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/70746.html

相關(guān)文章

  • java源碼

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

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

    摘要:所謂拉鏈法就是將鏈表和數(shù)組相結(jié)合。若遇到哈希沖突,則將沖突的值加到鏈表中即可。在編寫程序中,要盡量避免。 目錄: 0-1. 簡介 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 評論0 收藏0
  • 這幾道Java集合框架面試題在面試中幾乎必問

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

    bigdevil_s 評論0 收藏0
  • 3分鐘搞掂Set集合

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

    widuu 評論0 收藏0
  • ConcurrentHashMap基于JDK1.8源碼剖析

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

    sanyang 評論0 收藏0

發(fā)表評論

0條評論

kamushin233

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<