HashMap閱讀筆記

簡(jiǎn)介

HashMap是一個(gè)比較常用的鍵值對(duì)集合,在開(kāi)發(fā)中常用于映射關(guān)系。以下分析是基于Android中API25下的源碼

public class HashMap<K,V>
    extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable
{

HashMap屬于map族類,并且默認(rèn)實(shí)現(xiàn)Serializable,可用于網(wǎng)絡(luò)傳輸和本地序列化。

基礎(chǔ)屬性

    /**
     * HashMap的默認(rèn)初始容量,必須是2的倍數(shù)
     */
    static final int DEFAULT_INITIAL_CAPACITY = 4;

    /**
     * HashMap的最大容量,必須為2的倍數(shù)
     */
    static final int MAXIMUM_CAPACITY = 1 << 30;

    /**
     * 默認(rèn)的擴(kuò)容因子
     */
    static final float DEFAULT_LOAD_FACTOR = 0.75f;

    /**
     * HashMap為空時(shí)候共享的空數(shù)組
     */
    static final HashMapEntry<?,?>[] EMPTY_TABLE = {};

    /**
     * HashMap里面的存儲(chǔ)數(shù)組
     * 每一個(gè)數(shù)據(jù)都是一個(gè)HashMapEntry,而HashMapEntry本質(zhì)上是一個(gè)單項(xiàng)鏈表
     */
    transient HashMapEntry<K,V>[] table = (HashMapEntry<K,V>[]) EMPTY_TABLE;

    /**
     * HashMap中鍵值對(duì)的數(shù)量
     */
    transient int size;

    /**
     * 臨界值決定什么時(shí)候進(jìn)行擴(kuò)容(當(dāng)前容量 * 擴(kuò)容因子)
     */
    int threshold;

    /**
     * 擴(kuò)容因子,一個(gè)比例,相對(duì)于當(dāng)前容量來(lái)說(shuō)
     * Android中只會(huì)使用0.75,而會(huì)忽視其它的數(shù)值
     */
    final float loadFactor = DEFAULT_LOAD_FACTOR;

這里可以看到HashMap的基礎(chǔ)存儲(chǔ)結(jié)構(gòu):

存儲(chǔ)結(jié)構(gòu)

后面會(huì)詳細(xì)看到具體的意義。

構(gòu)造

    /**
     * 創(chuàng)建一個(gè)空的HashMap
     *
     * @param  initialCapacity 初始化容量
     * @param  loadFactor      擴(kuò)容因子,在Android中實(shí)際上并沒(méi)有用,還是采用默認(rèn)的0.75f
     */
    public HashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                    initialCapacity);
        //容量不能小于默認(rèn)容量和大于最大容量
        if (initialCapacity > MAXIMUM_CAPACITY) {
            initialCapacity = MAXIMUM_CAPACITY;//1 << 30
        } else if (initialCapacity < DEFAULT_INITIAL_CAPACITY) {
            initialCapacity = DEFAULT_INITIAL_CAPACITY;//4
        }

        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                    loadFactor);

        // 實(shí)際上每次擴(kuò)容的時(shí)候threshold都會(huì)重新計(jì)算
        threshold = initialCapacity;
        init();//默認(rèn)空實(shí)現(xiàn),子類可以嘗試實(shí)現(xiàn)這個(gè)方法做一些插入之類的操作
    }

    /**
     * 創(chuàng)建一個(gè)空的HashMap,容量為4,擴(kuò)容因子0.75
     */
    public HashMap() {
        this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
    }

    /**
     * 從其它的HashMap上面復(fù)制一份
     *
     * @param   m 想要復(fù)制的HashMap
     */
    public HashMap(Map<? extends K, ? extends V> m) {
        this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
                DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);
        inflateTable(threshold);

        putAllForCreate(m);
    }

    /**
     * 當(dāng)HashMap為空的時(shí)候初始化當(dāng)前HashMap
     */
    private void inflateTable(int toSize) {
        // Find a power of 2 >= toSize
        int capacity = roundUpToPowerOf2(toSize);
        //下一次擴(kuò)容的大小為當(dāng)前容量*擴(kuò)容因子
        float thresholdFloat = capacity * loadFactor;
        if (thresholdFloat > MAXIMUM_CAPACITY + 1) {
            thresholdFloat = MAXIMUM_CAPACITY + 1;
        }

        threshold = (int) thresholdFloat;
        table = new HashMapEntry[capacity];//構(gòu)建一個(gè)容量大小的數(shù)組
    }

    /**
     * 找到一個(gè)比number大的2的倍數(shù)
     */
    private static int roundUpToPowerOf2(int number) {
        int rounded = number >= MAXIMUM_CAPACITY
                ? MAXIMUM_CAPACITY
                : (rounded = Integer.highestOneBit(number)) != 0
                ? (Integer.bitCount(number) > 1) ? rounded << 1 : rounded
                : 1;
        /*稍微拆解一下方便理解
        if(number >= MAXIMUM_CAPACITY){
            rounded = MAXIMUM_CAPACITY;//不允許超過(guò)最大容量
        }else{
            //首先將number轉(zhuǎn)為2進(jìn)制表現(xiàn)形式,比方說(shuō)00100101
            //通過(guò)highestOneBit則rounded為00100000
            //找出最高位的1,其余位樸0
            rounded = Integer.highestOneBit(number);
            if(rounded != 0){
                //如果number的2進(jìn)制中不止1個(gè)1,比方說(shuō)00100101
                //因?yàn)閔ighestOneBit其余位樸0,得到rounded為00100000
                //所以獲得要大于number的2的倍數(shù),需要再乘以2,則rounded左移一位即可
                rounded = ((Integer.bitCount(number) > 1) ? rounded << 1 : rounded);
            }else{//如果number為0,容量取1即可
                rounded = 1;
            }
        }*/
        return rounded;
    }

    /**
     * 將指定map的數(shù)據(jù)放入當(dāng)前HashMap中
     * @param m 需要添加的數(shù)據(jù)
     */
    private void putAllForCreate(Map<? extends K, ? extends V> m) {
        //實(shí)際上就是遍歷一遍指定map,然后一個(gè)個(gè)添加到當(dāng)前HashMap中
        for (Map.Entry<? extends K, ? extends V> e : m.entrySet())
            putForCreate(e.getKey(), e.getValue());
    }

    /**
     * 該方法不會(huì)重新進(jìn)行擴(kuò)容操作
     * 只有在構(gòu)造的時(shí)候復(fù)制map之類的場(chǎng)景下才會(huì)使用
     */
    private void putForCreate(K key, V value) {
        //計(jì)算key的hash值
        int hash = null == key ? 0 : sun.misc.Hashing.singleWordWangJenkinsHash(key);
        //計(jì)算出當(dāng)前key在table的下標(biāo)
        int i = indexFor(hash, table.length);
        //下標(biāo)已經(jīng)計(jì)算成功
        //在當(dāng)前table的鏈表中先查找是否有相同key的元素,有的話直接設(shè)置,不新建
        for (HashMapEntry<K,V> e = table[i]; e != null; e = e.next) {//遍歷單向鏈表
            Object k;
            //key的hash值相同
            //key是同一個(gè)引用或者key的equals相同
            //此時(shí)直接復(fù)寫(xiě)value即可
            if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k)))) {
                e.value = value;
                return;
            }
        }
        //當(dāng)前table的鏈表中沒(méi)有當(dāng)前key,在鏈表當(dāng)中新建一個(gè)節(jié)點(diǎn)
        createEntry(hash, key, value, i);
    }

    /**
     * 通過(guò)key的hash值和當(dāng)前HashMap的容量
     * 計(jì)算出當(dāng)前node在table中的下標(biāo)
     */
    static int indexFor(int h, int length) {
        //假設(shè)h:0000 1110 length:0000 1000
        //0000 1110 h
        //0000 0111 length - 1,剛好將前面的所有位樸1
        //0000 0110 結(jié)果
        //結(jié)果必然是小于length
        //而且從這個(gè)計(jì)算也知道,length必須是2的倍數(shù)
        return h & (length-1);
    }

    /**
     * 在指定的鏈表末尾添加一個(gè)節(jié)點(diǎn)
     * @param hash 當(dāng)前要添加節(jié)點(diǎn)的key的hash值
     * @param key 當(dāng)前要添加的節(jié)點(diǎn)的key
     * @param value 當(dāng)前要添加的節(jié)點(diǎn)的value
     * @param bucketIndex 當(dāng)前鏈表在table[]中的下標(biāo)位置
     */
    void createEntry(int hash, K key, V value, int bucketIndex) {
        HashMapEntry<K,V> e = table[bucketIndex];//從table中獲取鏈表
        table[bucketIndex] = new HashMapEntry<>(hash, key, value, e);//新建一個(gè)HashMapEntry并且添加到鏈表中
        size++;//HashMap的大小+1
    }

從HashMap的拷貝型構(gòu)造函數(shù)的實(shí)現(xiàn)可以看出,為了更快的計(jì)算table[]的下標(biāo),HashMap中的容量都是2的倍數(shù),每一個(gè)table[i]都維持著一個(gè)鏈表。

鏈表結(jié)構(gòu)

先看一下table[i]中的鏈表結(jié)構(gòu)

    /** 
     * Android添加的
     * 單向鏈表結(jié)構(gòu),每一次新建的時(shí)候會(huì)把節(jié)點(diǎn)放在鏈表的頭部
     * @hide 
     * */
    static class HashMapEntry<K,V> implements Map.Entry<K,V> {
        final K key;//當(dāng)前節(jié)點(diǎn)的key
        V value;//當(dāng)前節(jié)點(diǎn)的value
        HashMapEntry<K,V> next;//當(dāng)前節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)
        int hash;//當(dāng)前節(jié)點(diǎn)的key的hash值

        /**
         * 創(chuàng)建一個(gè)新的節(jié)點(diǎn),并且將當(dāng)前節(jié)點(diǎn)放到指定鏈表的頭部
         */
        HashMapEntry(int h, K k, V v, HashMapEntry<K,V> n) {
            value = v;
            next = n;//這樣實(shí)現(xiàn)的效率極高
            key = k;
            hash = h;
        }
        
        //...省略一些
        
    }

HashMapEntry一個(gè)單向鏈表結(jié)構(gòu),每一次新建的時(shí)候直接把當(dāng)前節(jié)點(diǎn)放到指定鏈表的頭部,操作效率極高。

基本操作

    /**
     * 將指定鍵值對(duì)添加到當(dāng)前HashMap中
     * @param key 鍵值
     * @param value 鍵值
     * @return 如果當(dāng)前是覆蓋操作,返回被覆蓋的原來(lái)的value,否則為null
     */
    public V put(K key, V value) {
        if (table == EMPTY_TABLE) {//當(dāng)前HashMap為空
            inflateTable(threshold);//初始化HashMap,此時(shí)會(huì)構(gòu)建好table[]、容量和擴(kuò)容值
        }
        if (key == null)//HashMap支持null作為key
            return putForNullKey(value);
        //計(jì)算key的hash值
        int hash = sun.misc.Hashing.singleWordWangJenkinsHash(key);
        //通過(guò)hash值計(jì)算出對(duì)應(yīng)table[]下標(biāo)
        int i = indexFor(hash, table.length);
        //先嘗試復(fù)寫(xiě)當(dāng)前table[]的鏈表中“相同”的key的value
        for (HashMapEntry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            //這里可以看到如果hash值直接相等是效率最高的
            //否則走equals
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }

        modCount++;
        //當(dāng)前鏈表中沒(méi)有指定key的節(jié)點(diǎn),新建一個(gè)節(jié)點(diǎn)并且添加到鏈表當(dāng)中
        addEntry(hash, key, value, i);
        return null;
    }

    /**
     * 當(dāng)添加到HashMap中的鍵值對(duì)的key為null的時(shí)候
     * @param value key為null對(duì)應(yīng)的value
     * @return 如果當(dāng)前是覆蓋操作,返回被覆蓋的原來(lái)的value,否則為null
     */
    private V putForNullKey(V value) {
        //可以看到key為null的時(shí)候,默認(rèn)hash為0
        //先遍歷table[0]的鏈表,看是否需要覆蓋
        for (HashMapEntry<K,V> e = table[0]; e != null; e = e.next) {
            if (e.key == null) {//如果當(dāng)前節(jié)點(diǎn)key為null,直接覆蓋value
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
        modCount++;
        addEntry(0, null, value, 0);//否則新建一個(gè)節(jié)點(diǎn)并且添加到鏈表當(dāng)中
        return null;
    }

    /**
     * 新建一個(gè)節(jié)點(diǎn)并且添加到指定的鏈表的頭部
     * @param hash 需要添加的key的hash值
     * @param key 需要添加的節(jié)點(diǎn)的key
     * @param value 需要添加的節(jié)點(diǎn)的value
     * @param bucketIndex 需要添加到table[]的下標(biāo)
     */
    void addEntry(int hash, K key, V value, int bucketIndex) {
        //先判斷是否需要進(jìn)行擴(kuò)容操作
        if ((size >= threshold) && (null != table[bucketIndex])) {
            //當(dāng)前大小已經(jīng)到了容量*擴(kuò)容因子的標(biāo)準(zhǔn)
            //以當(dāng)前容量為基準(zhǔn),直接擴(kuò)大一倍的方式進(jìn)行擴(kuò)容
            resize(2 * table.length);
            //重新計(jì)算hash值
            hash = (null != key) ? sun.misc.Hashing.singleWordWangJenkinsHash(key) : 0;
            //根據(jù)最新的table[]的長(zhǎng)度計(jì)算table[]下標(biāo)
            bucketIndex = indexFor(hash, table.length);
        }
        //新建節(jié)點(diǎn)并放在放在table[bucketIndex]鏈表的頭部,大小+1
        createEntry(hash, key, value, bucketIndex);
    }

    /**
     * 擴(kuò)容操作
     * 當(dāng)HashMap添加數(shù)據(jù)的時(shí)候發(fā)現(xiàn)已經(jīng)到了擴(kuò)容標(biāo)準(zhǔn)
     * 則進(jìn)行擴(kuò)容
     * @param newCapacity 當(dāng)前希望的新的容量
     */
    void resize(int newCapacity) {
        HashMapEntry[] oldTable = table;//記錄舊的table[]
        int oldCapacity = oldTable.length;//記錄舊的容量
        if (oldCapacity == MAXIMUM_CAPACITY) {//擴(kuò)容前的容量已經(jīng)是最大容量,無(wú)法進(jìn)行擴(kuò)容
            threshold = Integer.MAX_VALUE;//修改擴(kuò)容基準(zhǔn)為最大容量
            return;
        }
        //根據(jù)當(dāng)前擴(kuò)容后的容量新建一個(gè)table[]
        HashMapEntry[] newTable = new HashMapEntry[newCapacity];
        transfer(newTable);
        table = newTable;//轉(zhuǎn)換table[]為最新的表
        //容量變化,重新計(jì)算下一次擴(kuò)容的基準(zhǔn)
        threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
    }

    /**
     * 當(dāng)進(jìn)行擴(kuò)容的時(shí)候,需要將舊的table[]數(shù)據(jù)轉(zhuǎn)移到當(dāng)前新的table[]上面
     * @param newTable 擴(kuò)容后新建的table[]
     */
    void transfer(HashMapEntry[] newTable) {
        int newCapacity = newTable.length;//擴(kuò)容后的大小
        //遍歷舊的table[],將舊的節(jié)點(diǎn)轉(zhuǎn)移到新的table[]中
        for (HashMapEntry<K,V> e : table) {
            while(null != e) {//遍歷當(dāng)前table[i]鏈表
                HashMapEntry<K,V> next = e.next;
                //通過(guò)每一個(gè)節(jié)點(diǎn)的hash值重新計(jì)算table[]的下標(biāo)
                int i = indexFor(e.hash, newCapacity);
                //將當(dāng)前節(jié)點(diǎn)放到新的table[]的鏈表的頭部
                e.next = newTable[i];
                newTable[i] = e;
                e = next;
            }
        }
    }

    /**
     * 從HashMap中獲得指定key對(duì)應(yīng)的value
     * @param key 需要查找的key,支持null
     * @return value,沒(méi)有的話為null
     */
    public V get(Object key) {
        if (key == null)//key為空,直接查找table[0]即可,相對(duì)于getEntry節(jié)省了indexFor的開(kāi)銷
            return getForNullKey();
        Map.Entry<K,V> entry = getEntry(key);

        return null == entry ? null : entry.getValue();
    }

    /**
     * 當(dāng)key為null的時(shí)候,獲取指定的value
     * 如果有key為null的節(jié)點(diǎn),必然位于table[0]的鏈表且唯一
     * @return value,沒(méi)有的話為null
     */
    private V getForNullKey() {
        if (size == 0) {//當(dāng)前HashMap還沒(méi)有數(shù)據(jù)
            return null;
        }
        //遍歷table[0],嘗試獲得key為null的節(jié)點(diǎn)的value
        for (HashMapEntry<K,V> e = table[0]; e != null; e = e.next) {
            if (e.key == null)
                return e.value;
        }
        return null;
    }

    /**
     * 根據(jù)指定的key獲得HashMap中對(duì)應(yīng)的節(jié)點(diǎn)的value
     */
    final Map.Entry<K,V> getEntry(Object key) {
        if (size == 0) {//當(dāng)前HashMap還沒(méi)有數(shù)據(jù)
            return null;
        }
        //計(jì)算key的hash值
        int hash = (key == null) ? 0 : sun.misc.Hashing.singleWordWangJenkinsHash(key);
        //先計(jì)算hash值對(duì)應(yīng)的table的下標(biāo)
        //然后遍歷對(duì)應(yīng)table[]的鏈表
        for (HashMapEntry<K,V> e = table[indexFor(hash, table.length)];
             e != null;
             e = e.next) {
            Object k;
            //這里可以看到如果hash值直接相等是效率最高的
            //否則走equals
            if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                return e;
        }
        return null;
    }

    /**
     * 從HashMap中移除指定key的節(jié)點(diǎn)
     * @param key 鍵值對(duì)中的key
     * @return 被移除的key節(jié)點(diǎn)對(duì)應(yīng)的value
     */
    public V remove(Object key) {
        Map.Entry<K,V> e = removeEntryForKey(key);
        return (e == null ? null : e.getValue());
    }

    /**
     * 從HashMap中移除指定key的節(jié)點(diǎn)
     * @param key 鍵值對(duì)中的key
     * @return 被移除的key節(jié)點(diǎn)
     */
    final Map.Entry<K,V> removeEntryForKey(Object key) {
        if (size == 0) {//當(dāng)前HashMap還沒(méi)有數(shù)據(jù)
            return null;
        }
        //計(jì)算key的hash值
        int hash = (key == null) ? 0 : sun.misc.Hashing.singleWordWangJenkinsHash(key);
        //通過(guò)hash值計(jì)算出對(duì)應(yīng)table[]下標(biāo)
        int i = indexFor(hash, table.length);
        //記錄當(dāng)前table[]鏈表的頭結(jié)點(diǎn)
        HashMapEntry<K,V> prev = table[i];
        HashMapEntry<K,V> e = prev;
        //從頭開(kāi)始遍歷當(dāng)前鏈表
        while (e != null) {
            HashMapEntry<K,V> next = e.next;//獲得當(dāng)前節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)
            Object k;
            //這里可以看到如果hash值直接相等是效率最高的
            //否則走equals
            if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k)))) {
                //刪除當(dāng)前節(jié)點(diǎn)
                modCount++;
                size--;//HashMap大小-1
                if (prev == e)//當(dāng)前需要?jiǎng)h除的節(jié)點(diǎn)是鏈表的頭結(jié)點(diǎn)
                    table[i] = next;//直接標(biāo)記當(dāng)前鏈表的頭結(jié)點(diǎn)為下一個(gè)節(jié)點(diǎn)
                else//刪除鏈表非頭結(jié)點(diǎn)的節(jié)點(diǎn)
                    prev.next = next;//將當(dāng)前節(jié)點(diǎn)的上一個(gè)節(jié)點(diǎn)直接指向當(dāng)前節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn),從而當(dāng)前節(jié)點(diǎn)從鏈表中移除
                e.recordRemoval(this);
                return e;
            }
            prev = e;//記錄當(dāng)前節(jié)點(diǎn)為前置節(jié)點(diǎn)
            e = next;//繼續(xù)遍歷下一個(gè)節(jié)點(diǎn)
        }
        //如果遍歷之后沒(méi)有找到對(duì)應(yīng)的key,此時(shí)鏈表的最后一個(gè)節(jié)點(diǎn)為null,這里直接返回null即可
        return e;
    }

    /**
     * 查找是否包含某一個(gè)key的節(jié)點(diǎn)
     */
    public boolean containsKey(Object key) {
        //實(shí)際上就是查找一邊當(dāng)前key的節(jié)點(diǎn),如果節(jié)點(diǎn)不為空則包含
        return getEntry(key) != null;
    }

HashMap的核心操作就是put和get,并且支持key為null的情況。
put的時(shí)候首先嘗試覆蓋舊的節(jié)點(diǎn),所以要先計(jì)算hash值找到對(duì)應(yīng)鏈表,然后遍歷鏈表尋找,如果沒(méi)有,則需要新建,在新建之前要判斷是否達(dá)到容量*擴(kuò)容因子的基準(zhǔn),如果達(dá)到,首先要進(jìn)行擴(kuò)容操作。
擴(kuò)容會(huì)將當(dāng)前容量增加一倍的大小,然后還需要遍歷將舊的table[]鏈表中的節(jié)點(diǎn)轉(zhuǎn)移到新的table[]中,并且這個(gè)過(guò)程中還需要根據(jù)hash重新計(jì)算節(jié)點(diǎn)位于table[]的下標(biāo)。
get操作首先計(jì)算key對(duì)應(yīng)的hash值,然后獲得對(duì)應(yīng)的table[]的節(jié)點(diǎn)鏈表,進(jìn)而遍歷鏈表查找對(duì)應(yīng)的節(jié)點(diǎn)并且返回value。

總結(jié)

從上面可以看到,HashMap中會(huì)有不少遍歷鏈表的操作,這個(gè)其實(shí)就說(shuō)明hash散列的重要性,如果不同的值但是最后放到了同一個(gè)table[i]的鏈表中,會(huì)導(dǎo)致某一個(gè)鏈表特別長(zhǎng),這樣對(duì)于遍歷效率來(lái)說(shuō)不是太好。
擴(kuò)容操作需要轉(zhuǎn)移舊的節(jié)點(diǎn)等操作,所以說(shuō)對(duì)于已知初始容量的HashMap來(lái)說(shuō),最好是指定容量,避免多余的擴(kuò)容操作。
HashMap并不是線程安全的,如果多個(gè)線程對(duì)鏈表進(jìn)行操作,會(huì)造成不可預(yù)期問(wèn)題,此時(shí)應(yīng)該嘗試ConcurrentHashMap或者Collections.synchronizedMap()。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • HashMap 是 Java 面試必考的知識(shí)點(diǎn),面試官?gòu)倪@個(gè)小知識(shí)點(diǎn)就可以了解我們對(duì) Java 基礎(chǔ)的掌握程度。網(wǎng)...
    野狗子嗷嗷嗷閱讀 6,810評(píng)論 9 107
  • 這是轉(zhuǎn)載的筆記。文章來(lái)源:http://www.canyin88.com/baike/31287/zhongguo...
    時(shí)茶閱讀 17,615評(píng)論 0 6
  • 凌晨昏昏不能眠,作詩(shī)一首。晨起甚早,食湯麵及雞子。上午處理雜務(wù),然後坐地鐵、高鐵至珠海,又於橫琴島上處理技術(shù)工作,...
    寒窗寄傲生閱讀 199評(píng)論 0 0
  • 前兩年的韓寒的一部電影里有句臺(tái)詞,當(dāng)時(shí)挺流行的,「為什么我聽(tīng)了很多道理,卻依然過(guò)不好一生。」相信很多人都同感。 我...
    wanbin閱讀 822評(píng)論 1 2

友情鏈接更多精彩內(nèi)容