HashMap-jdk1.7

    @Test
    public void asda() {
        Map<String, String> map = new HashMap<>();
        map.put("1", "2");
    }

首先從最常用的構造方法開始

 public HashMap() {
        this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
    }
    public HashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);

        this.loadFactor = loadFactor;
        threshold = initialCapacity;
        init();
    }

loadFactor為負載因子,默認值0.75.

    /**
     * The default initial capacity - MUST be a power of two.
     */
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
    /**
     * The load factor used when none specified in constructor.
     */
    static final float DEFAULT_LOAD_FACTOR = 0.75f;

接下來以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.hashmap存儲元素的本質是一個數(shù)組宴杀。inflateTable可以看到數(shù)組的初始化是在第一次put的時候進行的

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

關于數(shù)組大小,為2的冪次方蜗元,如果初始化的時候不是2的冪次方或渤,會向上取2的冪次方.比如說初始指定大小10,roundUpToPowerOf2函數(shù)運算會返回2^4=16奕扣。

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

threshold為負載容量薪鹦,當對象個數(shù)超過這個值并滿足 一定條件的時候就會進行resize操作。
initHashSeedAsNeeded主要用于hashseed的計算惯豆,和key的hash值計算有關池磁。
2.先不管key為null的情況,先看hash,indexFor,即hashmap的key的hash散列過程楷兽。

    final int hash(Object k) {
        int h = hashSeed;
        if (0 != h && k instanceof String) {
            return sun.misc.Hashing.stringHash32((String) k);
        }

        h ^= k.hashCode();

        // This function ensures that hashCodes that differ only by
        // constant multiples at each bit position have a bounded
        // number of collisions (approximately 8 at default load factor).
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }

可以看到非string的key的hash都是一系列的位運算地熄,主要和之前說過的hashseed有關。

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

散列過程采用的是與運算芯杀,這樣散列出來的值范圍為[0,length)端考。
3.經(jīng)過上面的過程,得出i就是元素在數(shù)組中的位置下標揭厚。如果兩個不一樣的key的散列值一樣却特,就會在同一個數(shù)組位置上采用鏈表結構。數(shù)組元素的結構如下筛圆。

 Entry(int h, K k, V v, Entry<K,V> n) {
            value = v;
            next = n;
            key = k;
            hash = h;
  }

由此不難理解下面這段代碼的作用是為了覆蓋同一個key的元素裂明。

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

if (e.hash == hash && ((k = e.key) == key || key.equals(k)))從這段代碼可以看出hashmap對于key重復的判定是hash值相等并且key相等(==用于基本類型的比較;比的是對象的內存地址,非基本類型調用equals方法)
4.如果key沒有重復太援,那就要進行新插入操作addEntry漾岳。

    void addEntry(int hash, K key, V value, int bucketIndex) {
        if ((size >= threshold) && (null != table[bucketIndex])) {
            resize(2 * table.length);
            hash = (null != key) ? hash(key) : 0;
            bucketIndex = indexFor(hash, table.length);
        }

        createEntry(hash, key, value, bucketIndex);
    }

當對象數(shù)量>=負載容量threshold并且當前元素的下表對應的數(shù)組位置不為空的時候就會進行擴容操作,擴容的規(guī)則為*2。擴容的過程中又會重新計算下hashseed粉寞。

  void resize(int newCapacity) {
        Entry[] oldTable = table;
        int oldCapacity = oldTable.length;
        if (oldCapacity == MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return;
        }

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

transfer就是個將數(shù)據(jù)從舊數(shù)組搬到新數(shù)組的過程。

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

這里指的注意的是最好三行代碼左腔,這個順序可以看出:在key的hash散列值一樣的時候唧垦,新插入元素會在表頭,即數(shù)組元素的位置上液样。比如說table[3]=a,a.next=b.這時候來了個c振亮,key的hash散列值和a一樣巧还,就會變成table[3]=c,c.next=a,a.next=b。這一點從后面的插入操作createEntry也可以看出來坊秸。

    void createEntry(int hash, K key, V value, int bucketIndex) {
        Entry<K,V> e = table[bucketIndex];
        table[bucketIndex] = new Entry<>(hash, key, value, e);
        size++;
    }

5.這時候再來看key為null的操作就非常簡單了麸祷。

 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;
    }
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市褒搔,隨后出現(xiàn)的幾起案子阶牍,更是在濱河造成了極大的恐慌,老刑警劉巖星瘾,帶你破解...
    沈念sama閱讀 212,816評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件走孽,死亡現(xiàn)場離奇詭異,居然都是意外死亡琳状,警方通過查閱死者的電腦和手機磕瓷,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來念逞,“玉大人困食,你說我怎么就攤上這事◆岢校” “怎么了硕盹?”我有些...
    開封第一講書人閱讀 158,300評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長审洞。 經(jīng)常有香客問我莱睁,道長,這世上最難降的妖魔是什么芒澜? 我笑而不...
    開封第一講書人閱讀 56,780評論 1 285
  • 正文 為了忘掉前任仰剿,我火速辦了婚禮,結果婚禮上痴晦,老公的妹妹穿的比我還像新娘南吮。我一直安慰自己,他們只是感情好誊酌,可當我...
    茶點故事閱讀 65,890評論 6 385
  • 文/花漫 我一把揭開白布部凑。 她就那樣靜靜地躺著,像睡著了一般碧浊。 火紅的嫁衣襯著肌膚如雪涂邀。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 50,084評論 1 291
  • 那天箱锐,我揣著相機與錄音比勉,去河邊找鬼。 笑死,一個胖子當著我的面吹牛浩聋,可吹牛的內容都是我干的观蜗。 我是一名探鬼主播,決...
    沈念sama閱讀 39,151評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼衣洁,長吁一口氣:“原來是場噩夢啊……” “哼墓捻!你這毒婦竟也來了?” 一聲冷哼從身側響起坊夫,我...
    開封第一講書人閱讀 37,912評論 0 268
  • 序言:老撾萬榮一對情侶失蹤砖第,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后践樱,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體厂画,經(jīng)...
    沈念sama閱讀 44,355評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,666評論 2 327
  • 正文 我和宋清朗相戀三年拷邢,在試婚紗的時候發(fā)現(xiàn)自己被綠了袱院。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,809評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡瞭稼,死狀恐怖忽洛,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情环肘,我是刑警寧澤欲虚,帶...
    沈念sama閱讀 34,504評論 4 334
  • 正文 年R本政府宣布,位于F島的核電站悔雹,受9級特大地震影響复哆,放射性物質發(fā)生泄漏。R本人自食惡果不足惜腌零,卻給世界環(huán)境...
    茶點故事閱讀 40,150評論 3 317
  • 文/蒙蒙 一梯找、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧益涧,春花似錦锈锤、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至扭弧,卻和暖如春阎姥,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背鸽捻。 一陣腳步聲響...
    開封第一講書人閱讀 32,121評論 1 267
  • 我被黑心中介騙來泰國打工丁寄, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留氨淌,地道東北人。 一個月前我還...
    沈念sama閱讀 46,628評論 2 362
  • 正文 我出身青樓伊磺,卻偏偏與公主長得像,于是被迫代替她去往敵國和親删咱。 傳聞我的和親對象是個殘疾皇子屑埋,可洞房花燭夜當晚...
    茶點故事閱讀 43,724評論 2 351

推薦閱讀更多精彩內容