亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專欄INFORMATION COLUMN

Java HashMap 源碼解析

Aklman / 2008人閱讀

摘要:所以利用哈希表這種數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)具體類時(shí),需要設(shè)計(jì)個(gè)好的函數(shù),使沖突盡可能的減少其次是需要解決發(fā)生沖突后如何處理。源碼剖析首先從構(gòu)造函數(shù)開始講,遵循集合框架的約束,提供了一個(gè)參數(shù)為空的構(gòu)造函數(shù)與有一個(gè)參數(shù)且參數(shù)類型為的構(gòu)造函數(shù)。

本文章首發(fā)于個(gè)人博客,鑒于sf博客樣式具有賞心悅目的美感,遂發(fā)表于此,供大家學(xué)習(xí)、批評(píng)。
本文還在不斷更新中,最新版可移至個(gè)人博客。?

繼上一篇文章Java集合框架綜述后,今天正式開始分析具體集合類的代碼,首先以既熟悉又陌生的HashMap開始。

簽名(signature)
public class HashMap
       extends AbstractMap
       implements Map, Cloneable, Serializable

可以看到HashMap繼承了

標(biāo)記接口Cloneable,用于表明HashMap對(duì)象會(huì)重寫java.lang.Object#clone()方法,HashMap實(shí)現(xiàn)的是淺拷貝(shallow copy)。

標(biāo)記接口Serializable,用于表明HashMap對(duì)象可以被序列化

比較有意思的是,HashMap同時(shí)繼承了抽象類AbstractMap與接口Map,因?yàn)槌橄箢?b>AbstractMap的簽名為

public abstract class AbstractMap implements Map

Stack Overfloooow上解釋到:

在語法層面繼承接口Map是多余的,這么做僅僅是為了讓閱讀代碼的人明確知道HashMap是屬于Map體系的,起到了文檔的作用

AbstractMap相當(dāng)于個(gè)輔助類,Map的一些操作這里面已經(jīng)提供了默認(rèn)實(shí)現(xiàn),后面具體的子類如果沒有特殊行為,可直接使用AbstractMap提供的實(shí)現(xiàn)。

Cloneable接口
It"s evil, don"t use it.

Cloneable這個(gè)接口設(shè)計(jì)的非常不好,最致命的一點(diǎn)是它里面竟然沒有clone方法,也就是說我們自己寫的類完全可以實(shí)現(xiàn)這個(gè)接口的同時(shí)不重寫clone方法。

關(guān)于Cloneable的不足,大家可以去看看《Effective Java》一書的作者給出的理由,在所給鏈接的文章里,Josh Bloch也會(huì)講如何實(shí)現(xiàn)深拷貝比較好,我這里就不在贅述了。

Map接口

在eclipse中的outline面板可以看到Map接口里面包含以下成員方法與內(nèi)部類:

可以看到,這里的成員方法不外乎是“增刪改查”,這也反映了我們編寫程序時(shí),一定是以“數(shù)據(jù)”為導(dǎo)向的。

在上篇文章講了Map雖然并不是Collection,但是它提供了三種“集合視角”(collection views),與下面三個(gè)方法一一對(duì)應(yīng):

Set keySet(),提供key的集合視角

Collection values(),提供value的集合視角

Set> entrySet(),提供key-value序?qū)Φ募弦暯?,這里用內(nèi)部類Map.Entry表示序?qū)?/p>

AbstractMap抽象類

AbstractMap對(duì)Map中的方法提供了一個(gè)基本實(shí)現(xiàn),減少了實(shí)現(xiàn)Map接口的工作量。
舉例來說:

如果要實(shí)現(xiàn)個(gè)不可變(unmodifiable)的map,那么只需繼承AbstractMap,然后實(shí)現(xiàn)其entrySet方法,這個(gè)方法返回的set不支持add與remove,同時(shí)這個(gè)set的迭代器(iterator)不支持remove操作即可。

相反,如果要實(shí)現(xiàn)個(gè)可變(modifiable)的map,首先繼承AbstractMap,然后重寫(override)AbstractMap的put方法,同時(shí)實(shí)現(xiàn)entrySet所返回set的迭代器的remove方法即可。

設(shè)計(jì)理念(design concept) 哈希表(hash table)

HashMap是一種基于哈希表(hash table)實(shí)現(xiàn)的map,哈希表(也叫關(guān)聯(lián)數(shù)組)一種通用的數(shù)據(jù)結(jié)構(gòu),大多數(shù)的現(xiàn)代語言都原生支持,其概念也比較簡(jiǎn)單:key經(jīng)過hash函數(shù)作用后得到一個(gè)槽(buckets或slots)的索引(index),槽中保存著我們想要獲取的值,如下圖所示

很容易想到,一些不同的key經(jīng)過同一hash函數(shù)后可能產(chǎn)生相同的索引,也就是產(chǎn)生了沖突,這是在所難免的。
所以利用哈希表這種數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)具體類時(shí),需要:

設(shè)計(jì)個(gè)好的hash函數(shù),使沖突盡可能的減少

其次是需要解決發(fā)生沖突后如何處理。

后面會(huì)重點(diǎn)介紹HashMap是如何解決這兩個(gè)問題的。

HashMap的一些特點(diǎn)

線程非安全,并且允許key與value都為null值,HashTable與之相反,為線程安全,key與value都不允許null值。

不保證其內(nèi)部元素的順序,而且隨著時(shí)間的推移,同一元素的位置也可能改變(resize的情況)

put、get操作的時(shí)間復(fù)雜度為O(1)。

遍歷其集合視角的時(shí)間復(fù)雜度與其容量(capacity,槽的個(gè)數(shù))和現(xiàn)有元素的大?。╡ntry的個(gè)數(shù))成正比,所以如果遍歷的性能要求很高,不要把capactiy設(shè)置的過高或把平衡因子(load factor,當(dāng)entry數(shù)大于capacity*loadFactor時(shí),會(huì)進(jìn)行resize,reside會(huì)導(dǎo)致key進(jìn)行rehash)設(shè)置的過低。

由于HashMap是線程非安全的,這也就是意味著如果多個(gè)線程同時(shí)對(duì)一hashmap的集合試圖做迭代時(shí)有結(jié)構(gòu)的上改變(添加、刪除entry,只改變entry的value的值不算結(jié)構(gòu)改變),那么會(huì)報(bào)ConcurrentModificationException,專業(yè)術(shù)語叫fail-fast,盡早報(bào)錯(cuò)對(duì)于多線程程序來說是很有必要的。

Map m = Collections.synchronizedMap(new HashMap(...)); 通過這種方式可以得到一個(gè)線程安全的map。

源碼剖析

首先從構(gòu)造函數(shù)開始講,HashMap遵循集合框架的約束,提供了一個(gè)參數(shù)為空的構(gòu)造函數(shù)與有一個(gè)參數(shù)且參數(shù)類型為Map的構(gòu)造函數(shù)。除此之外,還提供了兩個(gè)構(gòu)造函數(shù),用于設(shè)置HashMap的容量(capacity)與平衡因子(loadFactor)。

    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();
    }
    public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }
    public HashMap() {
        this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
    }

從代碼上可以看到,容量與平衡因子都有個(gè)默認(rèn)值,并且容量有個(gè)最大值

    /**
     * The default initial capacity - MUST be a power of two.
     */
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

    /**
     * The maximum capacity, used if a higher value is implicitly specified
     * by either of the constructors with arguments.
     * MUST be a power of two <= 1<<30.
     */
    static final int MAXIMUM_CAPACITY = 1 << 30;

    /**
     * The load factor used when none specified in constructor.
     */
    static final float DEFAULT_LOAD_FACTOR = 0.75f;

可以看到,默認(rèn)的平衡因子為0.75,這是權(quán)衡了時(shí)間復(fù)雜度與空間復(fù)雜度之后的最好取值(JDK說是最好的?),過高的因子會(huì)降低存儲(chǔ)空間但是查找(lookup,包括HashMap中的put與get方法)的時(shí)間就會(huì)增加。

這里比較奇怪的是問題:容量必須為2的指數(shù)倍(默認(rèn)為16),這是為什么呢?解答這個(gè)問題,需要了解HashMap中哈希函數(shù)的設(shè)計(jì)原理。

哈希函數(shù)的設(shè)計(jì)原理
   /**
     * Retrieve object hash code and applies a supplemental hash function to the
     * result hash, which defends against poor quality hash functions.  This is
     * critical because HashMap uses power-of-two length hash tables, that
     * otherwise encounter collisions for hashCodes that do not differ
     * in lower bits. Note: Null keys always map to hash 0, thus index 0.
     */
    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);
    }

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

看到這么多位操作,是不是覺得暈頭轉(zhuǎn)向了呢,還是搞清楚原理就行了,畢竟位操作速度是很快的,不能因?yàn)椴缓美斫饩筒挥昧?。
網(wǎng)上說這個(gè)問題的也比較多,我這里根據(jù)自己的理解,盡量做到通俗易懂。

在哈希表容量(也就是buckets或slots大?。閘ength的情況下,為了使每個(gè)key都能在沖突最小的情況下映射到[0,length)(注意是左閉右開區(qū)間)的索引(index)內(nèi),一般有兩種做法:

讓length為素?cái)?shù),然后用hashCode(key) mod length的方法得到索引

讓length為2的指數(shù)倍,然后用hashCode(key) & (length-1)的方法得到索引

HashTable用的是方法1,HashMap用的是方法2。

因?yàn)楸酒黝}講的是HashMap,所以關(guān)于方法1為什么要用素?cái)?shù),我這里不想過多介紹,大家可以看這里。

重點(diǎn)說說方法2的情況,方法2其實(shí)也比較好理解:

因?yàn)閘ength為2的指數(shù)倍,所以length-1所對(duì)應(yīng)的二進(jìn)制位都為1,然后在與hashCode(key)做與運(yùn)算,即可得到[0,length)內(nèi)的索引

但是這里有個(gè)問題,如果hashCode(key)的大于length的值,而且hashCode(key)的二進(jìn)制位的低位變化不大,那么沖突就會(huì)很多,舉個(gè)例子:

Java中對(duì)象的哈希值都32位整數(shù),而HashMap默認(rèn)大小為16,那么有兩個(gè)對(duì)象那么的哈希值分別為:0xABAB00000xBABA0000,它們的后幾位都為0,那么與16與后得到的都是0,也就是產(chǎn)生了沖突。

造成沖突的原因關(guān)鍵在于16限制了只能用低位來計(jì)算,高位直接舍棄了,所以我們需要額外的哈希函數(shù)而不只是簡(jiǎn)單的對(duì)象的hashCode方法了。
具體來說,就是HashMap中hash函數(shù)干的事了

首先有個(gè)隨機(jī)的hashSeed,來降低沖突發(fā)生的幾率

然后如果是字符串,用了sun.misc.Hashing.stringHash32((String) k);來獲取索引值

最后,通過一系列無符號(hào)右移操作,來把高位與低位進(jìn)行或操作,來降低沖突發(fā)生的幾率

右移的偏移量20,12,7,4是怎么來的呢?因?yàn)镴ava中對(duì)象的哈希值都是32位的,所以這幾個(gè)數(shù)應(yīng)該就是把高位與低位做與運(yùn)算,至于這幾個(gè)數(shù)是如何選取的,就不清楚了,網(wǎng)上搜了半天也沒統(tǒng)一且讓人信服的說法,大家可以參考下面幾個(gè)鏈接:

http://stackoverflow.com/questions/7922019/openjdks-rehashing-mechanism/7922219#7922219

http://stackoverflow.com/questions/9335169/understanding-strange-java-hash-function/9336103#9336103

http://stackoverflow.com/questions/14453163/can-anybody-explain-how-java-design-hashmaps-hash-function/14479945#14479945

HashMap.Entry

HashMap中存放的是HashMap.Entry對(duì)象,它繼承自Map.Entry,其比較重要的是構(gòu)造函數(shù)

    static class Entry implements Map.Entry {
        final K key;
        V value;
        Entry next;
        int hash;

        Entry(int h, K k, V v, Entry n) {
            value = v;
            next = n;
            key = k;
            hash = h;
        }
        // setter, getter, equals, toString 方法省略
        public final int hashCode() {
            //用key的hash值與上value的hash值作為Entry的hash值
            return Objects.hashCode(getKey()) ^ Objects.hashCode(getValue());
        }
        /**
         * This method is invoked whenever the value in an entry is
         * overwritten by an invocation of put(k,v) for a key k that"s already
         * in the HashMap.
         */
        void recordAccess(HashMap m) {
        }

        /**
         * This method is invoked whenever the entry is
         * removed from the table.
         */
        void recordRemoval(HashMap m) {
        }
    }

可以看到,Entry實(shí)現(xiàn)了單向鏈表的功能,用next成員變量來級(jí)連起來。

介紹完Entry對(duì)象,下面要說一個(gè)比較重要的成員變量

    /**
     * The table, resized as necessary. Length MUST Always be a power of two.
     */
    //HashMap內(nèi)部維護(hù)了一個(gè)為數(shù)組類型的Entry變量table,用來保存添加進(jìn)來的Entry對(duì)象
    transient Entry[] table = (Entry[]) EMPTY_TABLE;

你也許會(huì)疑問,Entry不是單向鏈表嘛,怎么這里又需要個(gè)數(shù)組類型的table呢?
我翻了下之前的算法書,其實(shí)這是解決沖突的一個(gè)方式:開散列法(鏈地址法),效果如下:

就是相同索引值的Entry,會(huì)以單向鏈表的形式存在

鏈地址法的可視化

網(wǎng)上找到個(gè)很好的網(wǎng)站,用來可視化各種常見的算法,很棒。瞬間覺得國(guó)外大學(xué)比國(guó)內(nèi)的強(qiáng)不知多少倍。
下面的鏈接可以模仿哈希表采用鏈地址法解決沖突,大家可以自己去玩玩?

https://www.cs.usfca.edu/~galles/visualization/OpenHash.html

get操作

get操作相比put操作簡(jiǎn)單,所以先介紹get操作

    public V get(Object key) {
        //多帶帶處理key為null的情況
        if (key == null)
            return getForNullKey();
        Entry entry = getEntry(key);
    
        return null == entry ? null : entry.getValue();
    }
    private V getForNullKey() {
        if (size == 0) {
            return null;
        }
        //key為null的Entry用于放在table[0]中,但是在table[0]沖突鏈中的Entry的key不一定為null
        //所以需要遍歷沖突鏈,查找key是否存在
        for (Entry e = table[0]; e != null; e = e.next) {
            if (e.key == null)
                return e.value;
        }
        return null;
    }
    final Entry getEntry(Object key) {
        if (size == 0) {
            return null;
        }

        int hash = (key == null) ? 0 : hash(key);
        //首先定位到索引在table中的位置
        //然后遍歷沖突鏈,查找key是否存在
        for (Entry e = table[indexFor(hash, table.length)];
             e != null;
             e = e.next) {
            Object k;
            if (e.hash == hash &&
                ((k = e.key) == key || (key != null && key.equals(k))))
                return e;
        }
        return null;
    }
put操作

因?yàn)閜ut操作有可能需要對(duì)HashMap進(jìn)行resize,所以實(shí)現(xiàn)略復(fù)雜些

    private void inflateTable(int toSize) {
        //輔助函數(shù),用于填充HashMap到指定的capacity
        // Find a power of 2 >= toSize
        int capacity = roundUpToPowerOf2(toSize);
        //threshold為resize的閾值,超過后HashMap會(huì)進(jìn)行resize,內(nèi)容的entry會(huì)進(jìn)行rehash
        threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
        table = new Entry[capacity];
        initHashSeedAsNeeded(capacity);
    }
    /**
     * 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.
     */
    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);
        //這里的循環(huán)是關(guān)鍵
        //當(dāng)新增的key所對(duì)應(yīng)的索引i,對(duì)應(yīng)table[i]中已經(jīng)有值時(shí),進(jìn)入循環(huán)體
        for (Entry e = table[i]; e != null; e = e.next) {
            Object k;
            //判斷是否存在本次插入的key,如果存在用本次的value替換之前oldValue
            //并返回之前的oldValue
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
        //如果本次新增key之前不存在于HashMap中,modCount加1,說明結(jié)構(gòu)改變了
        modCount++;
        addEntry(hash, key, value, i);
        return null;
    }
    void addEntry(int hash, K key, V value, int bucketIndex) {
        //如果增加一個(gè)元素會(huì)后,HashMap的大小超過閾值,需要resize
        if ((size >= threshold) && (null != table[bucketIndex])) {
            //增加的幅度是之前的1倍
            resize(2 * table.length);
            hash = (null != key) ? hash(key) : 0;
            bucketIndex = indexFor(hash, table.length);
        }

        createEntry(hash, key, value, bucketIndex);
    }
    void createEntry(int hash, K key, V value, int bucketIndex) {
        //首先得到該索引處的沖突鏈Entries,有可能為null,不為null
        Entry e = table[bucketIndex];
        //然后把新的Entry添加到?jīng)_突鏈的開頭,也就是說,后插入的反而在前面(第一次還真沒看明白)
        table[bucketIndex] = new Entry<>(hash, key, value, e);
        size++;
    }
    //下面看看HashMap是如何進(jìn)行resize,廬山真面目就要揭曉了?
    void resize(int newCapacity) {
        Entry[] oldTable = table;
        int oldCapacity = oldTable.length;
        //如果已經(jīng)達(dá)到最大容量,那么就直接返回
        if (oldCapacity == MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return;
        }

        Entry[] newTable = new Entry[newCapacity];
        //initHashSeedAsNeeded(newCapacity)的返回值決定了是否需要重新計(jì)算Entry的hash值
        transfer(newTable, initHashSeedAsNeeded(newCapacity));
        table = newTable;
        threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
    }

    /**
     * Transfers all entries from current table to newTable.
     */
    void transfer(Entry[] newTable, boolean rehash) {
        int newCapacity = newTable.length;
        //遍歷當(dāng)前的table,將里面的元素添加到新的newTable中
        for (Entry e : table) {
            while(null != e) {
                Entry next = e.next;
                if (rehash) {
                    e.hash = null == e.key ? 0 : hash(e.key);
                }
                int i = indexFor(e.hash, newCapacity);
                e.next = newTable[i];
                //最后這兩句用了與put放過相同的技巧
                //將后插入的反而在前面
                newTable[i] = e;
                e = next;
            }
        }
    }
    /**
     * Initialize the hashing mask value. We defer initialization until we
     * really need it.
     */
    final boolean initHashSeedAsNeeded(int capacity) {
        boolean currentAltHashing = hashSeed != 0;
        boolean useAltHashing = sun.misc.VM.isBooted() &&
                (capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
        //這里說明了,在hashSeed不為0或滿足useAltHash時(shí),會(huì)重算Entry的hash值
        //至于useAltHashing的作用可以參考下面的鏈接
        // http://stackoverflow.com/questions/29918624/what-is-the-use-of-holder-class-in-hashmap
        boolean switching = currentAltHashing ^ useAltHashing;
        if (switching) {
            hashSeed = useAltHashing
                ? sun.misc.Hashing.randomHashSeed(this)
                : 0;
        }
        return switching;
    }
    

一般而言,認(rèn)為HashMap中g(shù)et與put的時(shí)間復(fù)雜度為O(1),因?yàn)樗己玫膆ash函數(shù),保證了沖突發(fā)生的幾率比較小。

HashMap的序列化

介紹到這里,基本上算是把HashMap中一些核心的點(diǎn)講完了,但還有個(gè)比較嚴(yán)重的問題:保存Entry的table數(shù)組為transient的,也就是說在進(jìn)行序列化時(shí),并不會(huì)包含該成員,這是為什么呢?

transient Entry[] table = (Entry[]) EMPTY_TABLE;

為了解答這個(gè)問題,我們需要明確下面事實(shí):

Object.hashCode方法對(duì)于一個(gè)類的兩個(gè)實(shí)例返回的是不同的哈希值

我們可以試想下面的場(chǎng)景:

我們?cè)跈C(jī)器A上算出對(duì)象A的哈希值與索引,然后把它插入到HashMap中,然后把該HashMap序列化后,在機(jī)器B上重新算對(duì)象的哈希值與索引,這與機(jī)器A上算出的是不一樣的,所以我們?cè)跈C(jī)器B上get對(duì)象A時(shí),會(huì)得到錯(cuò)誤的結(jié)果。

所以說,當(dāng)序列化一個(gè)HashMap對(duì)象時(shí),保存Entry的table是不需要序列化進(jìn)來的,因?yàn)樗诹硪慌_(tái)機(jī)器上是錯(cuò)誤的。

因?yàn)檫@個(gè)原因,HashMap重現(xiàn)了writeObjectreadObject 方法

    private void writeObject(java.io.ObjectOutputStream s)
        throws IOException
    {
        // Write out the threshold, loadfactor, and any hidden stuff
        s.defaultWriteObject();

        // Write out number of buckets
        if (table==EMPTY_TABLE) {
            s.writeInt(roundUpToPowerOf2(threshold));
        } else {
           s.writeInt(table.length);
        }

        // Write out size (number of Mappings)
        s.writeInt(size);

        // Write out keys and values (alternating)
        if (size > 0) {
            for(Map.Entry e : entrySet0()) {
                s.writeObject(e.getKey());
                s.writeObject(e.getValue());
            }
        }
    }

    private static final long serialVersionUID = 362498820763181265L;

    private void readObject(java.io.ObjectInputStream s)
         throws IOException, ClassNotFoundException
    {
        // Read in the threshold (ignored), loadfactor, and any hidden stuff
        s.defaultReadObject();
        if (loadFactor <= 0 || Float.isNaN(loadFactor)) {
            throw new InvalidObjectException("Illegal load factor: " +
                                               loadFactor);
        }

        // set other fields that need values
        table = (Entry[]) EMPTY_TABLE;

        // Read in number of buckets
        s.readInt(); // ignored.

        // Read number of mappings
        int mappings = s.readInt();
        if (mappings < 0)
            throw new InvalidObjectException("Illegal mappings count: " +
                                               mappings);

        // capacity chosen by number of mappings and desired load (if >= 0.25)
        int capacity = (int) Math.min(
                    mappings * Math.min(1 / loadFactor, 4.0f),
                    // we have limits...
                    HashMap.MAXIMUM_CAPACITY);

        // allocate the bucket array;
        if (mappings > 0) {
            inflateTable(capacity);
        } else {
            threshold = capacity;
        }

        init();  // Give subclass a chance to do its thing.

        // Read the keys and values, and put the mappings in the HashMap
        for (int i = 0; i < mappings; i++) {
            K key = (K) s.readObject();
            V value = (V) s.readObject();
            putForCreate(key, value);
        }
    }
    private void putForCreate(K key, V value) {
        int hash = null == key ? 0 : hash(key);
        int i = indexFor(hash, table.length);

        /**
         * Look for preexisting entry for key.  This will never happen for
         * clone or deserialize.  It will only happen for construction if the
         * input Map is a sorted map whose ordering is inconsistent w/ equals.
         */
        for (Entry e = table[i]; e != null; e = e.next) {
            Object k;
            if (e.hash == hash &&
                ((k = e.key) == key || (key != null && key.equals(k)))) {
                e.value = value;
                return;
            }
        }

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

簡(jiǎn)單來說,在序列化時(shí),針對(duì)Entry的key與value分別多帶帶序列化,當(dāng)反序列化時(shí),再多帶帶處理即可。

總結(jié)

在總結(jié)完HashMap后,發(fā)現(xiàn)這里面一些核心的東西,像哈希表的沖突解決,都是算法課上學(xué)到,不過由于“年代久遠(yuǎn)”,已經(jīng)忘得差不多了,我覺得忘

一方面是由于時(shí)間久不用

另一方面是由于本身沒理解好

平時(shí)多去思考,這樣在遇到一些性能問題時(shí)也好排查。

還有一點(diǎn)就是我們?cè)诜治瞿承┚唧w類或方法時(shí),不要花太多時(shí)間一些細(xì)枝末節(jié)的邊界條件上,這樣很得不償失,倒不是說這么邊界條件不重要,程序的bug往往就是邊界條件沒考慮周全導(dǎo)致的。
只是說我們可以在理解了這個(gè)類或方法的總體思路后,再來分析這些邊界條件。
如果一開始就分析,那真是丈二和尚——摸不著頭腦了,隨著對(duì)它工作原理的加深,才有可能理解這些邊界條件的場(chǎng)景。

今天到此為止,下次打算分析TreeMap。Stay Tuned!?

PS:今天是反法西斯戰(zhàn)爭(zhēng)勝利70周年,雖然這是個(gè)和平的年代,但是我們?nèi)匀徊荒芡嗽?jīng)的傷痛,做好身邊的事,愛國(guó)從現(xiàn)在做起。??????

參考

http://supercoderz.in/understanding-transient-variables-in-java-and-how-they-are-practically-used-in-hashmap/

http://stackoverflow.com/questions/9144472/why-is-the-hash-table-of-hashmap-marked-as-transient-although-the-class-is-seria

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

轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/64480.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
  • Java集合之LinkedHashMap源碼解析

    摘要:底層基于拉鏈?zhǔn)降纳⒘薪Y(jié)構(gòu),并在中引入紅黑樹優(yōu)化過長(zhǎng)鏈表的問題。在其之上,通過維護(hù)一條雙向鏈表,實(shí)現(xiàn)了散列數(shù)據(jù)結(jié)構(gòu)的有序遍歷。 原文地址 LinkedHashMap LinkedHashMap繼承自HashMap實(shí)現(xiàn)了Map接口?;緦?shí)現(xiàn)同HashMap一樣,不同之處在于LinkedHashMap保證了迭代的有序性。其內(nèi)部維護(hù)了一個(gè)雙向鏈表,解決了 HashMap不能隨時(shí)保持遍歷順序和插...

    QiShare 評(píng)論0 收藏0
  • Java 集合Hashtable源碼深入解析

    摘要:分別獲取正序反序的鍵集。是用來實(shí)現(xiàn)機(jī)制的第部分源碼解析基于為了更了解的原理,下面對(duì)源碼代碼作出分析。實(shí)現(xiàn)了迭代器和枚舉兩個(gè)接口獲取的迭代器若的實(shí)際大小為則返回空迭代器對(duì)象否則,返回正常的的對(duì)象。 概要 前面,我們已經(jīng)系統(tǒng)的對(duì)List進(jìn)行了學(xué)習(xí)。接下來,我們先學(xué)習(xí)Map,然后再學(xué)習(xí)Set;因?yàn)镾et的實(shí)現(xiàn)類都是基于Map來實(shí)現(xiàn)的(如,HashSet是通過HashMap實(shí)現(xiàn)的,TreeSe...

    Turbo 評(píng)論0 收藏0
  • Java集合之HashMap源碼解析

    摘要:之前,其內(nèi)部是由數(shù)組鏈表來實(shí)現(xiàn)的,而對(duì)于鏈表長(zhǎng)度超過的鏈表將轉(zhuǎn)儲(chǔ)為紅黑樹。非線程安全,即任一時(shí)刻可以有多個(gè)線程同時(shí)寫,可能會(huì)導(dǎo)致數(shù)據(jù)的不一致。有時(shí)兩個(gè)會(huì)定位到相同的位置,表示發(fā)生了碰撞。 原文地址 HashMap HashMap 是 Map 的一個(gè)實(shí)現(xiàn)類,它代表的是一種鍵值對(duì)的數(shù)據(jù)存儲(chǔ)形式。 大多數(shù)情況下可以直接定位到它的值,因而具有很快的訪問速度,但遍歷順序卻是不確定的。 HashM...

    lindroid 評(píng)論0 收藏0
  • Java TreeMap 源碼解析

    摘要:源碼剖析由于紅黑樹的操作我這里不說了,所以這里基本上也就沒什么源碼可以講了,因?yàn)檫@里面重要的算法都是,這里的是指,他們是算法導(dǎo)論的作者,也就是說里面算法都是參照算法導(dǎo)論的偽代碼。因?yàn)榧t黑樹是平衡的二叉搜索樹,所以其包含操作的時(shí)間復(fù)雜度都為。 本文章首發(fā)于個(gè)人博客,鑒于sf博客樣式具有賞心悅目的美感,遂發(fā)表于此,供大家學(xué)習(xí)、批評(píng)。本文還在不斷更新中,最新版可移至個(gè)人博客。? 繼上篇文章...

    rubyshen 評(píng)論0 收藏0
  • Java相關(guān)

    摘要:本文是作者自己對(duì)中線程的狀態(tài)線程間協(xié)作相關(guān)使用的理解與總結(jié),不對(duì)之處,望指出,共勉。當(dāng)中的的數(shù)目而不是已占用的位置數(shù)大于集合番一文通版集合番一文通版垃圾回收機(jī)制講得很透徹,深入淺出。 一小時(shí)搞明白自定義注解 Annotation(注解)就是 Java 提供了一種元程序中的元素關(guān)聯(lián)任何信息和著任何元數(shù)據(jù)(metadata)的途徑和方法。Annotion(注解) 是一個(gè)接口,程序可以通過...

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

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

0條評(píng)論

閱讀需要支付1元查看
<