JDK源碼-Map系列

Map

  • 類圖

TreeMap實現(xiàn)

  • TreeMap是通過紅黑樹進行的,紅黑樹能夠保證在最壞的情況,基本的動態(tài)集合操作的時間復(fù)雜度為O(lgn);

  • TreeMap是根據(jù)鍵的自然順序進行排序的,或者根據(jù)創(chuàng)建映射時提供的 Comparator 進行排序摇锋,具體取決于使用的構(gòu)造方法胆建。

  • TreeMap的基本操作 containsKey雕拼、get钠至、put 和 remove 的時間復(fù)雜度是 log(n) 房铭。另外荆残,TreeMap是非同步的蛛枚。 它的iterator 方法返回的迭代器是fail-fastl的暖哨。

  • jdk里邊的實現(xiàn)分析請看這篇文章Java提高篇(二七)-----TreeMap, Java 集合系列12之 TreeMap詳細介紹(源碼解析)和使用示例由于紅黑樹這部分太過復(fù)雜,需要好好研究之后再給大家分享.

HashMap實現(xiàn)

  • 它是基于哈希表的Map接口的實現(xiàn).了解hashMap前先看下hashcode的定義:
hashcode方法返回該對象的哈希碼值。支持該方法是為哈希表提供一些優(yōu)點墨状,例如,java.util.Hashtable 提供的哈希表菲饼。
hashCode 的常規(guī)協(xié)定是:
在 Java 應(yīng)用程序執(zhí)行期間肾砂,在同一對象上多次調(diào)用 hashCode 方法時,必須一致地返回相同的整數(shù)宏悦,前提是對象上 equals 比較中所用的信息沒有被修改镐确。從某一應(yīng)用程序的一次執(zhí)行到同一應(yīng)用程序的另一次執(zhí)行,該整數(shù)無需保持一致饼煞。
如果根據(jù) equals(Object) 方法源葫,兩個對象是相等的,那么在兩個對象中的每個對象上調(diào)用 hashCode 方法都必須生成相同的整數(shù)結(jié)果砖瞧。
以下情況不 是必需的:如果根據(jù) equals(java.lang.Object) 方法息堂,兩個對象不相等,那么在兩個對象中的任一對象上調(diào)用 hashCode 方法必定會生成不同的整數(shù)結(jié)果块促。但是储矩,程序員應(yīng)該知道,為不相等的對象生成不同整數(shù)結(jié)果可以提高哈希表的性能褂乍。
實際上持隧,由 Object 類定義的 hashCode 方法確實會針對不同的對象返回不同的整數(shù)。(這一般是通過將該對象的內(nèi)部地址轉(zhuǎn)換成一個整數(shù)來實現(xiàn)的逃片,但是 JavaTM 編程語言不需要這種實現(xiàn)技巧屡拨。)
當(dāng)equals方法被重寫時,通常有必要重寫 hashCode 方法褥实,以維護 hashCode 方法的常規(guī)協(xié)定呀狼,該協(xié)定聲明相等對象必須具有相等的哈希碼。
  • 總結(jié)來說:

如果兩個對象相同貌踏,就是適用于equals(java.lang.Object) 方法,那么這兩個對象的hashCode一定要相同窟勃;

如果對象的equals方法被重寫祖乳,那么對象的hashCode也盡量重寫,并且產(chǎn)生hashCode使用的對象秉氧,一定要和equals方法中使用的一致眷昆,否則就會違反上面提到的第2點;

兩個對象的hashCode相同,并不一定表示兩個對象就相同亚斋,也就是不一定適用于equals(java.lang.Object) 方法作媚,只能夠說明這兩個對象在散列存儲結(jié)構(gòu)中,如Hashtable帅刊,他們“存放在同一個籃子里”纸泡。
  • HashMap有幾個重要的屬性:table, size, threshold, loadFactor, modCount。
    table:
 存儲Entry對象的數(shù)組,Node對象實現(xiàn)了Entry.在必要的時候,會進行擴容.Node對象本身是一個內(nèi)嵌鏈表.
 transient Node<K,V>[] table;

size:

map包含數(shù)據(jù)的個數(shù)
transient int size;

modcount:

map被修改的次數(shù),用來實現(xiàn)fail-fast機制
transient int modCount;

threshold:

閾值厚掷,用于判斷是否需要調(diào)整HashMap的容量弟灼。threshold的值="容量*加載因子"级解,當(dāng)HashMap中存儲數(shù)據(jù)的數(shù)量達到threshold時冒黑,就需要將HashMap的容量加倍。
int threshold;

loadFactor:

map的負載因子
final float loadFactor;

看一下Node的定義:它繼承了Map中的Entry接口,定義了key,value,下一個節(jié)點的引用,以及hash值.由此及上文的Node數(shù)組,HashMap底層是一個數(shù)組,每一個數(shù)組的元素都是一個單向鏈表.


class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Node<K,V> next;
}

那么問題來了:數(shù)組下表對應(yīng)的Key的hash值,hash值一般都很大,那數(shù)組也要那么大嗎?
Key的hash算法如下:納莫,通過下文中的擾動函數(shù)得到一個hash,再通過數(shù)組大小邏輯與hash:
(n - 1) & hash
得到數(shù)組下標(biāo).
參考文章JDK 源碼中 HashMap 的 hash 方法原理是什么勤哗?

static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
  • 設(shè)計理念:
    HashMap進行存儲的時候,將key進行hash散列為一個int值,將int值設(shè)為底層數(shù)組的下標(biāo),將key的hash值相同的Entry存入到該元素的鏈表上.
    那么通過key進行查找的時候,就可以通過key的hash值,先去數(shù)組找到對應(yīng)元素,再通過元素尋找Entry;

  • HashMap 擴容方法

final Node<K,V>[] resize() {
        //原始的table
        Node<K,V>[] oldTab = table;
        //原始table的大小
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        //原始table的閾值
        int oldThr = threshold;
        int newCap, newThr = 0;
        if (oldCap > 0) {
            if (oldCap >= MAXIMUM_CAPACITY) {
              //如果原來table的大小大于定義的最大容量1 << 30(2^30),將閾值設(shè)為計算機能標(biāo)識的最大整數(shù)
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                     //如果原始table的大小的二倍小于默認的最大容量,并且大于默認的最小容量,則將閾值翻倍并返回
                newThr = oldThr << 1; // double threshold
        }else if (oldThr > 0) 
        //如果原來的table大小等于0且原始閾值大于0,那么,table的大小賦值為原始閾值
            newCap = oldThr;
        else {               
            // 如果原始閾值不大于0,且原始table容量不大于0
            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"})
        //通過新的table容量創(chuàng)建table
            Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        table = newTab;
        if (oldTab != null) {
          //將原來的table數(shù)據(jù)copy到新建的table中
            for (int j = 0; j < oldCap; ++j) {
                Node<K,V> e;
                if ((e = oldTab[j]) != null) {
                    oldTab[j] = null;
                    if (e.next == null)
                    //如果當(dāng)前節(jié)點沒有下一個元素
                        newTab[e.hash & (newCap - 1)] = e;
                    else if (e instanceof TreeNode)
                    //如果當(dāng)前節(jié)點是TreeNode,那么就通過紅黑樹存入.
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    else { // preserve order
                      //循環(huán)copy
                        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;
    }

LinkedHashMap實現(xiàn)

  • LinkedHashMap在邏輯關(guān)系上是通過外層的鏈表控制,空間關(guān)系上仍然是通過和HashMap一樣的hash桶進行存儲,

  • LinkedHashMap是基于HashMap的一個實現(xiàn),它保證了HashMap元素沒有的有序性,LinkedHashMap可以讓元素實現(xiàn)兩種有序的方式:當(dāng)AccessOrder=true時,元素按照訪問的先后順序進行排序,AccessOrder=false時,元素按照插入的順序進行排序.它是由雙向鏈表+hashmap實現(xiàn)的.

/**
     * Constructs an empty <tt>LinkedHashMap</tt> instance with the
     * specified initial capacity, load factor and ordering mode.
     *
     * @param  initialCapacity the initial capacity
     * @param  loadFactor      the load factor
     * @param  accessOrder     the ordering mode - <tt>true</tt> for
     *         access-order, <tt>false</tt> for insertion-order
     * @throws IllegalArgumentException if the initial capacity is negative
     *         or the load factor is nonpositive
     */
    public LinkedHashMap(int initialCapacity,
                         float loadFactor,
                         boolean accessOrder) {
        super(initialCapacity, loadFactor);
        this.accessOrder = accessOrder;
    }
  • LinkedHashMap如何基于HashMap進行的擴展?
    通過查看源碼,LinkedHashMap進行的時候,并沒有重寫hashMap的put,remove等方法,那么怎么保證LinkedHashMap的put和remove操作不同于HashMap.原因就是這三個方法:


// Callbacks to allow LinkedHashMap post-actions
    void afterNodeAccess(Node<K,V> p) { }
    void afterNodeInsertion(boolean evict) { }
    void afterNodeRemoval(Node<K,V> p) { }

HashMap在實現(xiàn)putVal,remove等方法的時候,調(diào)用了這幾個空方法.這樣LinkedHashMap通過重載這幾個方法,來實現(xiàn)了自己的一些邏輯.

  • 從afterNodeInsertion(boolean evict)看出的LinkedHashMap對外提供的擴展的接口.
void afterNodeInsertion(boolean evict) { // possibly remove eldest
        LinkedHashMap.Entry<K,V> first;
        if (evict && (first = head) != null && removeEldestEntry(first)) {
            K key = first.key;
            removeNode(hash(key), key, null, false, true);
        }
    }
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
        return false;
    }
  • 外部通過重載方法removeEldestEntry(),可以自定義移除老元素的規(guī)則.如LruCache一樣的擴展實現(xiàn)
public class LRUCache2<K, V> extends LinkedHashMap<K, V> {
    private final int MAX_CACHE_SIZE;
    public LRUCache2(int cacheSize) {
        super((int) Math.ceil(cacheSize / 0.75) + 1, 0.75f, true);
        MAX_CACHE_SIZE = cacheSize;
    }

    @Override
    protected boolean removeEldestEntry(Map.Entry eldest) {
        return size() > MAX_CACHE_SIZE;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        for (Map.Entry<K, V> entry : entrySet()) {
            sb.append(String.format("%s:%s ", entry.getKey(), entry.getValue()));
        }
        return sb.toString();
    }
}

參考來源:java之LinkedHashMap實現(xiàn)原理

  • LinkedHashMap的迭代
    LinkedHashMap是對Array與Link的折衷處理抡爹,Array與Link可以說是兩個速度方向的極端,Array注重于數(shù)據(jù)的獲取芒划,而處理修改(添加/刪除)的效率非常低冬竟;Link由于是每個對象都保持著下一個對象的指針,查找某個數(shù)據(jù)需要遍歷之前所有的數(shù)據(jù)民逼,所以效率比較低泵殴,而在修改操作中比較快。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末拼苍,一起剝皮案震驚了整個濱河市笑诅,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌疮鲫,老刑警劉巖吆你,帶你破解...
    沈念sama閱讀 216,919評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異俊犯,居然都是意外死亡妇多,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,567評論 3 392
  • 文/潘曉璐 我一進店門燕侠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來者祖,“玉大人,你說我怎么就攤上這事绢彤∠贪” “怎么了?”我有些...
    開封第一講書人閱讀 163,316評論 0 353
  • 文/不壞的土叔 我叫張陵杖虾,是天一觀的道長烂瘫。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么坟比? 我笑而不...
    開封第一講書人閱讀 58,294評論 1 292
  • 正文 為了忘掉前任芦鳍,我火速辦了婚禮,結(jié)果婚禮上葛账,老公的妹妹穿的比我還像新娘柠衅。我一直安慰自己,他們只是感情好籍琳,可當(dāng)我...
    茶點故事閱讀 67,318評論 6 390
  • 文/花漫 我一把揭開白布菲宴。 她就那樣靜靜地躺著,像睡著了一般趋急。 火紅的嫁衣襯著肌膚如雪喝峦。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,245評論 1 299
  • 那天呜达,我揣著相機與錄音谣蠢,去河邊找鬼。 笑死查近,一個胖子當(dāng)著我的面吹牛眉踱,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播霜威,決...
    沈念sama閱讀 40,120評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼谈喳,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了戈泼?” 一聲冷哼從身側(cè)響起婿禽,我...
    開封第一講書人閱讀 38,964評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎矮冬,沒想到半個月后谈宛,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,376評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡胎署,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,592評論 2 333
  • 正文 我和宋清朗相戀三年吆录,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片琼牧。...
    茶點故事閱讀 39,764評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡恢筝,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出巨坊,到底是詐尸還是另有隱情撬槽,我是刑警寧澤,帶...
    沈念sama閱讀 35,460評論 5 344
  • 正文 年R本政府宣布趾撵,位于F島的核電站侄柔,受9級特大地震影響共啃,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜暂题,卻給世界環(huán)境...
    茶點故事閱讀 41,070評論 3 327
  • 文/蒙蒙 一移剪、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧薪者,春花似錦纵苛、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,697評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至悬槽,卻和暖如春怀吻,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背陷谱。 一陣腳步聲響...
    開封第一講書人閱讀 32,846評論 1 269
  • 我被黑心中介騙來泰國打工烙博, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留瑟蜈,地道東北人烟逊。 一個月前我還...
    沈念sama閱讀 47,819評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像铺根,于是被迫代替她去往敵國和親宪躯。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,665評論 2 354

推薦閱讀更多精彩內(nèi)容