JAVA源碼閱讀之HashMap

據說面試時講自己讀過源碼會很不一樣

個人理解散列表大概就是可以通過某些函數計算出不同key在存儲的數組中對應的index,刷題的時候我們也會經常遇到需要存儲26個字母的情況,于是新建數組

int[] letter = new int[26];
letter[(char)item- 'a']++;

從而實現對字母的計數。這里字母letter對應的index可以由letter-'a'計算得到,a對應0,b對應1.......也算是一個散列表吧。。后來讀了書、 算法導論上叫他直接尋址表

在剛才特定場景下,我們知道這個數組只需要存儲26個數字,因此能夠直接新建數組,但是當全域非常大時,這樣的直接尋址表明顯會很浪費。于是推出主角散列表。
散列表中需要有散列函數將key值全域映射到存儲散列表的數組的各個位置上。
于是首要的問題就變成了

可能不同的key值會被映射到相同的位置上

plan A 鏈接法:將存儲數組變為List[ ]的形式,使用散列函數得到在數組中的index后,將當前key-value對插入該List中。數組中每個元素都是一個可長可短的鏈表。查詢一個key值最多需要遍歷最長的那個鏈表一次

plan B 開放尋址法(散列函數2.0):將散列函數的輸入擴充為key值和訪問次數,當數組對應的index中key值與要查找的不同時,下次會生成新的index。


基礎知識就到這里吧。。散列函數怎么寫。。目前我感覺是一個過于算法的問題了。。書上也講了很多
HashMap 繼承AbstractMap, 實現Map<K,V>, Cloneable, Serializable 三個接口

    /**
     * The number of key-value mappings contained in this map.
     */
    transient int size; // 所有key-value對的個數
    /**
     * The table, resized as necessary. Length MUST Always be a power of two.
     */
    transient HashMapEntry<K,V>[] table = (HashMapEntry<K,V>[]) EMPTY_TABLE;

這個table的length程序寫的一定要是2的整數冪,至于為什么之后會提到。

使用HashMapEntry類型的數組

這個類型中有key、value、next、hash。這里明顯用到了書上講的第一個方法了,使用鏈表存儲


get(Object key) 方法

對于get(Object key) 方法,會調用getForNullKey(key == null) 或getEntry(key);
在getEntry(key)中

   /**
     * Returns the entry associated with the specified key in the
     * HashMap.  Returns null if the HashMap contains no mapping
     * for the key.
     */
    final Entry<K,V> getEntry(Object key) {
        if (size == 0) { //根本沒有元素存儲在里面
            return null;
        }

        int hash = (key == null) ? 0 : sun.misc.Hashing.singleWordWangJenkinsHash(key);
       //一個感覺帶了人名的hash函數
        for (HashMapEntry<K,V> e = table[indexFor(hash, table.length)];
             e != null;
             e = e.next) { //當前HashMapEntry的next
            Object k;
            if (e.hash == hash && //判斷hash值
                ((k = e.key) == key || (key != null && key.equals(k))))//判斷key值
                return e;
        }
        return null;
    }

如果return e說明找到了Entry<K,V>,順利返回。

這個判斷key值的環節

(k = e.key) == key 可以判斷出e.key與 當前key是否== 如果兩者本就是相同的引用,則會返回true。因此很明顯不要更改一個引用再把它多次作為key賦值。。會出事情的,像下面這樣。我們會覺得aa和bb其實不相等的。

StringBuilder aa = new StringBuilder("a");
StringBuilder bb = aa;
bb.setCharAt(0, 'b');
System.out.println(aa == bb); // true

不過真實情況下還有hash值同樣作為判斷條件


put(K key, V value) 方法
/**
     * Associates the specified value with the specified key in this map.
     * If the map previously contained a mapping for the key, the old
     * value is replaced.
     *
     * @param key key with which the specified value is to be associated
     * @param value value to be associated with the specified key
     * @return the previous value associated with <tt>key</tt>, or
     *         <tt>null</tt> if there was no mapping for <tt>key</tt>.
     *         (A <tt>null</tt> return can also indicate that the map
     *         previously associated <tt>null</tt> with <tt>key</tt>.)
     */
    public V put(K key, V value) {
        if (table == EMPTY_TABLE) {
            inflateTable(threshold); // 只有一個空表
        }
        if (key == null)
            return putForNullKey(value);
        int hash = sun.misc.Hashing.singleWordWangJenkinsHash(key);
        int i = indexFor(hash, table.length);
        for (HashMapEntry<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
            }
        }

        modCount++;
        addEntry(hash, key, value, i); // 沒找到,增加這個key-val對。這里面會size++的
        return null;
    }
我們關心一下indexFor函數
    /**
     * Returns index for hash code h.
     */
    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);// length為2的整數冪
    }

在這里length為2的整數冪可以保證不論hash值具體為多少,訪問數組總不會越界。(比如當length為8(1000),length-1 為(111) h & (length-1)返回h的低三位)另一個信息就是即使在同一個index下,他們的hash值不會完全相同。只是低位相同

但是這個indexFor函數還是讓人挺不放心的

int i = indexFor(hash, table.length);

當前元素的index是隨著hash值以及table.length變化的。。。這個問題還是挺尷尬的。。畢竟hash值我們認為只要函數不變每個key生成的hash值就一定不變。。但是table.length得要變的。。

當添加key-value對到達門限,會進行resize(2 * table.length);直接將table擴充一倍

   /**
     * Rehashes the contents of this map into a new array with a
     * larger capacity.  This method is called automatically when the
     * number of keys in this map reaches its threshold.
     *
     * If current capacity is MAXIMUM_CAPACITY, this method does not
     * resize the map, but sets threshold to Integer.MAX_VALUE.
     * This has the effect of preventing future calls.
     *
     * @param newCapacity the new capacity, MUST be a power of two;
     *        must be greater than current capacity unless current
     *        capacity is MAXIMUM_CAPACITY (in which case value
     *        is irrelevant).
     */
    void resize(int newCapacity) {
        HashMapEntry[] oldTable = table;
        int oldCapacity = oldTable.length;
        if (oldCapacity == MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return;
        }

        HashMapEntry[] newTable = new HashMapEntry[newCapacity];
        transfer(newTable);
        table = newTable;
        threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
    }

其中ransfer(HashMapEntry[] newTable) 將原表內數據遷移,根據HashMapEntry中的hash重新計算在新表中的index,構造新的鏈表數組進行存儲。


    /**
     * Transfers all entries from current table to newTable.
     */
    void transfer(HashMapEntry[] newTable) {
        int newCapacity = newTable.length;
        for (HashMapEntry<K,V> e : table) {
            while(null != e) {
                HashMapEntry<K,V> next = e.next;
                int i = indexFor(e.hash, newCapacity); //存儲element時必須保存hash值
                e.next = newTable[i]; //將當前元素放在該index的頭部,放在尾部會增加時間復雜度
                newTable[i] = e;
                e = next;
            }
        }
    }
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,461評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,538評論 3 417
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,423評論 0 375
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,991評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,761評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,207評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,268評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,419評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,959評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,782評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,983評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,528評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,222評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,653評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,901評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,678評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,978評論 2 374

推薦閱讀更多精彩內容

  • 一、基本數據類型 注釋 單行注釋:// 區域注釋:/* */ 文檔注釋:/** */ 數值 對于byte類型而言...
    龍貓小爺閱讀 4,283評論 0 16
  • 前言 這次我和大家一起學習HashMap,HashMap我們在工作中經常會使用,而且面試中也很頻繁會問到,因為它里...
    liangzzz閱讀 8,016評論 7 102
  • HashMap 是 Java 面試必考的知識點,面試官從這個小知識點就可以了解我們對 Java 基礎的掌握程度。網...
    野狗子嗷嗷嗷閱讀 6,681評論 9 107
  • 雖天茫茫,雨凄凄,可晴總會至 雖路漫漫,風颯颯,可春總會至 一時快意,歌舞一曲 一時失意,揮詩一首 平凡人生何牽記...
    一只特立獨行的豬與阿飛閱讀 193評論 0 2
  • 他問我:我是否喜歡他? 我說:猶豫說明你更愛自己。 另一個他向我說:我有點喜歡你。 我說:我更喜歡她。 她說:他會...
    重度面癱患者閱讀 271評論 0 0