Java中的Collection和Map(三)--Map體系


  Map集合,即我們常用的key-Value 集合,Map以鍵值對的形式來存儲數據,我們常用Map集合有:HashMap,TreeMap,WeakHashMap,EnumMap,LinkedHahMap,HashTable。他們都是以key-Value鍵值對形式存儲數據。

1、HashMap

  HashMap 底層采用Hash表結構來存儲數據的。

   /**
     * The table, resized as necessary. Length MUST Always be a power of two.
     */
    transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;

  我們存儲的數據就存放在Entry 類型的table數組中,Entry 是HashMap一內部類,他實現了接口Map.Entry<K,V> 接口。Entry結構跟我們前面說鏈表Node有點相似。

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

        /**
         * Creates new entry.
         */
        Entry(int h, K k, V v, Entry<K,V> n) {
            value = v;
            next = n;
            key = k;
            hash = h;
        }

        public final K getKey() {
            return key;
        }

        public final V getValue() {
            return value;
        }

        public final V setValue(V newValue) {
            V oldValue = value;
            value = newValue;
            return oldValue;
        }

        public final boolean equals(Object o) {
            if (!(o instanceof Map.Entry))
                return false;
            Map.Entry e = (Map.Entry)o;
            Object k1 = getKey();
            Object k2 = e.getKey();
            if (k1 == k2 || (k1 != null && k1.equals(k2))) {
                Object v1 = getValue();
                Object v2 = e.getValue();
                if (v1 == v2 || (v1 != null && v1.equals(v2)))
                    return true;
            }
            return false;
        }

        public final int hashCode() {
            return Objects.hashCode(getKey()) ^ Objects.hashCode(getValue());
        }

        public final String toString() {
            return getKey() + "=" + 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<K,V> m) {
        }

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

  當我們使用 new HashMap<K,V>(); 構造方法來創建一HashMap 時,檢測參數是否合法,內部初始化了一些成員變量。 

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

   當我們調用put(K k,V v) 方法的時候,底冊又是如何做的呢:

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

  從上面的代碼中我們可以看出,當我們首次向一個新創建的Map 中put 數據的時候,首先是通過 inflateTable 方法用來初始化用來存數據table數組的長度。初始化長度的同時通過initHashSeedAsNeeded 方法 來初始化哈希掩碼值。

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

   如果我們存放的 key-Value 鍵值對中的鍵值為null, 調用用putForNullKey(value) 方法 來存放我們的鍵值對。

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

  從代碼中我們可以看到,如果key 已存在於原來的map 中,將原來key所對應的value替換為我們新的value,並將原來的value 返回。如過不存在,則調用addEntry方法將我們的key-value 存入 ,並返回null(key)。

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

  addEntry 方法首先要做的事情是判斷是否需要 擴充table 的長度。如果需要的話調用resize 方法來擴充table 的長度,算出hash ,通過indexFor 方法算出元素所要存放於數組中對應的下標,通過createEntry 方法將元素存入Mpa中。

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

  如果 存如的 key-value 鍵值對key 不為空。首先通過indexFor  方法算出方法算出元素所要存放於數組中對應的下標i, 從i 開始向后查找元素是否存在於map中如果存在替換原map 中key 所對應的value 值,並將 舊值返回。如果不存在調用addEntry 方法存入。

  以上就是map put 值得過程。而當我們通過  value get(Key key)  方法來取值的時候是如何做的呢。

 public V get(Object key) {
        if (key == null)
            return getForNullKey();
        Entry<K,V> entry = getEntry(key);

        return null == entry ? null : entry.getValue();
    }
final Entry<K,V> getEntry(Object key) {
        if (size == 0) {
            return null;
        }

        int hash = (key == null) ? 0 : hash(key);
        for (Entry<K,V> 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 的流程,get 方法理解起來就一目了然了。

  下面我們再來看下我們通常用的 containsKey(Key key) 和containsValue(Value value)方法。

 public boolean containsKey(Object key) {
        return getEntry(key) != null;
    }
public boolean containsValue(Object value) {
        if (value == null)
            return containsNullValue();

        Entry[] tab = table;
        for (int i = 0; i < tab.length ; i++)
            for (Entry e = tab[i] ; e != null ; e = e.next)
                if (value.equals(e.value))
                    return true;
        return false;
    }

  是不是有種一目了然地感覺。

  綜上所述:我們知道了HashMap 底層數據結構是hash 表結構。我們講到了put 方法、get方法 以及containsValue 和containsKey 方法的內部實現。唯獨沒有說到Hash表到底是中結構,底層的hash 算法是如何實現的。這些會在后面的章節中慢慢說明。

2、TreeMap

  TreeMap 底層機構是一種屬結構。TreeMap 具有排序排序功能。 我們在使用TreeMap 的時候可以傳入我們自己的比較器,對其進行排序。如果不傳入的話TreeMap 會使用自己key 默認的比較器進行排序。如果我們想要是用自己定義的類型來作為TreeMap的key 的話 ,我們自定義的類需要實現Comparable 接口實現其compareTo(T o) 方法。或者在使用的時候我們傳入我們自己定義的比較器。下面我們就來看下TreeMap 的底層結構:

  跟HashMap 一樣TreeMap 底層也是用 Entry來存儲數據的,但是TreeMap 有自己的Entry 實現(樹)。

static final class Entry<K,V> implements Map.Entry<K,V> {
        K key;
        V value;
        Entry<K,V> left = null;
        Entry<K,V> right = null;
        Entry<K,V> parent;
        boolean color = BLACK;

        /**
         * Make a new cell with given key, value, and parent, and with
         * {@code null} child links, and BLACK color.
         */
        Entry(K key, V value, Entry<K,V> parent) {
            this.key = key;
            this.value = value;
            this.parent = parent;
        }

        /**
         * Returns the key.
         *
         * @return the key
         */
        public K getKey() {
            return key;
        }

        /**
         * Returns the value associated with the key.
         *
         * @return the value associated with the key
         */
        public V getValue() {
            return value;
        }

        /**
         * Replaces the value currently associated with the key with the given
         * value.
         *
         * @return the value associated with the key before this method was
         *         called
         */
        public V setValue(V value) {
            V oldValue = this.value;
            this.value = value;
            return oldValue;
        }

        public boolean equals(Object o) {
            if (!(o instanceof Map.Entry))
                return false;
            Map.Entry<?,?> e = (Map.Entry<?,?>)o;

            return valEquals(key,e.getKey()) && valEquals(value,e.getValue());
        }

        public int hashCode() {
            int keyHash = (key==null ? 0 : key.hashCode());
            int valueHash = (value==null ? 0 : value.hashCode());
            return keyHash ^ valueHash;
        }

        public String toString() {
            return key + "=" + value;
        }
    }

  知道了TreeMap 底層Entry 的實現我們再來看下  put 方法是如何向TreeMap 中存放數據的。

public V put(K key, V value) {
        Entry<K,V> t = root;
        if (t == null) {
            compare(key, key); // type (and possibly null) check

            root = new Entry<>(key, value, null);
            size = 1;
            modCount++;
            return null;
        }
        int cmp;
        Entry<K,V> parent;
        // split comparator and comparable paths
        Comparator<? super K> cpr = comparator;
        if (cpr != null) {
            do {
                parent = t;
                cmp = cpr.compare(key, t.key);
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else
                    return t.setValue(value);
            } while (t != null);
        }
        else {
            if (key == null)
                throw new NullPointerException();
            Comparable<? super K> k = (Comparable<? super K>) key;
            do {
                parent = t;
                cmp = k.compareTo(t.key);
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else
                    return t.setValue(value);
            } while (t != null);
        }
        Entry<K,V> e = new Entry<>(key, value, parent);
        if (cmp < 0)
            parent.left = e;
        else
            parent.right = e;
        fixAfterInsertion(e);
        size++;
        modCount++;
        return null;
    }

  當我們想一TreeMap 中存入第一個元素的時候,底層樹種不存在節點,這是我們創建一個節點做為樹的根節點。節點存放有key 和value 的值(Entry)。以后每次put 分為兩種情況。1、如果在我門 new TreeMap 的時候傳入了我們自己的比較器 ,就采用我們自己定義的比較器來比較key 值得大小,來確定元素在書中存放的位置。2、如果沒有傳入我們自己定義的比較器(這中情況下 我們的key 類型需要實現Comparable 接口)使用key 默認實現的 比較方法。

  在來看以下 TreeMap 的get 方法:

public V get(Object key) {
        Entry<K,V> p = getEntry(key);
        return (p==null ? null : p.value);
    }
 final Entry<K,V> getEntry(Object key) {
        // Offload comparator-based version for sake of performance
        if (comparator != null)
            return getEntryUsingComparator(key);
        if (key == null)
            throw new NullPointerException();
        Comparable<? super K> k = (Comparable<? super K>) key;
        Entry<K,V> p = root;
        while (p != null) {
            int cmp = k.compareTo(p.key);
            if (cmp < 0)
                p = p.left;
            else if (cmp > 0)
                p = p.right;
            else
                return p;
        }
        return null;
    }

  上面我們看到了 get 方法的實現。無非就是 通過我們自己傳入的比較器,或者key 自身的比較方法來來尋找相同的key 值。找到的話返回當前key 所對應的 value  找不到的話返回null。

  同樣containsKey 和 containsValue 實現思路差不多。

3、WeakHashMap

  WeakHashMap實現了Map接口,是HashMap的一種實現,他使用弱引用作為內部數據的存儲方案,WeakHashMap可以作為簡單緩存表的解決方案,當系統內存不夠的時候,垃圾收集器會自動的清除沒有在其他任何地方被引用的鍵值對。

  如果需要用一張很大的HashMap作為緩存表,那么可以考慮使用WeakHashMap,當鍵值不存在的時候添加到表中,存在即取出其值。

WeakHashMap weakMap = new WeakHashMap<Integer, byte[]>();
for(int i = 0; i < 100000; i++){
Integer ii = new Integer(i);
weakMap.put(ii, new byte[i]);
}

HashMap map = new HashMap<Integer, byte[]>();
  for (int i = 0; i < 10000; i++) {
  Integer ii = new Integer(i);
  map.put(ii, new byte[i]);
}

  這2段代碼分別用-Xmx2M的參數運行,運行的結果是第一段代碼可以很好的運行,第二段代碼會出現“Java Heap Space”的錯誤,這說明用WeakHashMap存儲,在系統內存不夠用的時候會自動回收內存。

  如果WeakHashMap的key在系統內持有強引用,那么WeakHashMap就退化為HashMap,所有的表項無法被垃圾收集器自動清理。

4、EnumMap

  與枚舉類型鍵一起使用的專用 Map 實現。枚舉映射中所有鍵都必須來自單個枚舉類型,該枚舉類型在創建映射時顯式或隱式地指定。枚舉映射在內部表示為數組。此表示形式非常緊湊且高效。

枚舉映射根據其鍵的自然順序 來維護(該順序是聲明枚舉常量的順序)。在 collection 視圖(keySet()entrySet()values())所返回的迭代器中反映了這一點。

由 collection 視圖返回的迭代器是弱一致 的:它們不會拋出 ConcurrentModificationException,也不一定顯示在迭代進行時發生的任何映射修改的效果。

不允許使用 null 鍵。試圖插入 null 鍵將拋出 NullPointerException。但是,試圖測試是否出現 null 鍵或移除 null 鍵將不會拋出異常。允許使用 null 值。

像大多數 collection 一樣,EnumMap 是不同步的。如果多個線程同時訪問一個枚舉映射,並且至少有一個線程修改該映射,則此枚舉映射在外部應該是同步的。這一般通過對自然封裝該枚舉映射的某個對象進行同步來完成。如果不存在這樣的對象,則應該使用 Collections.synchronizedMap(java.util.Map<k, v="">) 方法來“包裝”該枚舉。最好在創建時完成這一操作,以防止意外的非同步訪問:

     Map<EnumKey, V> m
         = Collections.synchronizedMap(new EnumMap<EnumKey, V>(...));
 

實現注意事項:所有基本操作都在固定時間內執行。雖然並不保證,但它們很可能比其 HashMap 副本更快。

  下面我們簡單來看下 EnumMap 底層簡單實現:

  先從構造方法:

private final Class<K> keyType;
private transient K[] keyUniverse;
private transient Object[] vals; 
public EnumMap(Class<K> keyType) {
        this.keyType = keyType;
        keyUniverse = getKeyUniverse(keyType);
        vals = new Object[keyUniverse.length];
    }
public EnumMap(EnumMap<K, ? extends V> m) {
        keyType = m.keyType;
        keyUniverse = m.keyUniverse;
        vals = m.vals.clone();
        size = m.size;
    }
public EnumMap(Map<K, ? extends V> m) {
        if (m instanceof EnumMap) {
            EnumMap<K, ? extends V> em = (EnumMap<K, ? extends V>) m;
            keyType = em.keyType;
            keyUniverse = em.keyUniverse;
            vals = em.vals.clone();
            size = em.size;
        } else {
            if (m.isEmpty())
                throw new IllegalArgumentException("Specified map is empty");
            keyType = m.keySet().iterator().next().getDeclaringClass();
            keyUniverse = getKeyUniverse(keyType);
            vals = new Object[keyUniverse.length];
            putAll(m);
        }
    }

  EnumMap 提供了三種不同參數的構造方法。

  再來看下put 方法:

public V put(K key, V value) {
        typeCheck(key);

        int index = key.ordinal();
        Object oldValue = vals[index];
        vals[index] = maskNull(value);
        if (oldValue == null)
            size++;
        return unmaskNull(oldValue);
    }
private Object maskNull(Object value) {
        return (value == null ? NULL : value);
    }
private V unmaskNull(Object value) {
        return (V) (value == NULL ? null : value);
    }

  從中我們可以看出put 方法插入值得時候 是想vals 數組中加入了數據。

  再看get 方法:

public V get(Object key) {
        return (isValidKey(key) ?
                unmaskNull(vals[((Enum)key).ordinal()]) : null);
    }
private boolean isValidKey(Object key) {
        if (key == null)
            return false;

        // Cheaper than instanceof Enum followed by getDeclaringClass
        Class keyClass = key.getClass();
        return keyClass == keyType || keyClass.getSuperclass() == keyType;
    }

  從上面我們可以看出:枚舉映射在內部表示為數組。

5、LinkedHashMap

 LinkedHashMap是HashMap的一個子類,它保留插入的順序,如果需要輸出的順序和輸入時的相同,那么就選用LinkedHashMap。

   LinkedHashMap是Map接口的哈希表和鏈接列表實現,具有可預知的迭代順序。此實現提供所有可選的映射操作,並允許使用null值和null鍵。此類不保證映射的順序,特別是它不保證該順序恆久不變
   LinkedHashMap實現與HashMap的不同之處在於,后者維護着一個運行於所有條目的雙重鏈接列表。此鏈接列表定義了迭代順序,該迭代順序可以是插入順序或者是訪問順序。

  對於LinkedHashMap而言,它繼承與HashMap、底層使用哈希表與雙向鏈表來保存所有元素。其基本操作與父類HashMap相似,它通過重寫父類相關的方法,來實現自己的鏈接列表特性。下面我們來分析LinkedHashMap的源代碼:

public class LinkedHashMap<K, V> extends HashMap<K, V> implements Map<K, V>  

  LinkedHashMap采用的hash算法和HashMap相同,但是它重新定義了數組中保存的元素Entry,該Entry除了保存當前對象的引用外,還保存了其上一個元素before和下一個元素after的引用,從而在哈希表的基礎上又構成了雙向鏈接列表。看源代碼:

//true表示按照訪問順序迭代,false時表示按照插入順序  
 private final boolean accessOrder;  
/** 
 * 雙向鏈表的表頭元素。 
 */  
private transient Entry<K,V> header;  
  
/** 
 * LinkedHashMap的Entry元素。 
 * 繼承HashMap的Entry元素,又保存了其上一個元素before和下一個元素after的引用。 
 */  
private static class Entry<K,V> extends HashMap.Entry<K,V> {  
    Entry<K,V> before, after;  
    ……  
}  

  HashMap.Entry:

static class Entry<K,V> implements Map.Entry<K,V> {  
        final K key;  
        V value;  
        Entry<K,V> next;  
        final int hash;  
  
        Entry(int h, K k, V v, Entry<K,V> n) {  
            value = v;  
            next = n;  
            key = k;  
            hash = h;  
        }  
}  

  通過源代碼可以看出,在LinkedHashMap的構造方法中,實際調用了父類HashMap的相關構造方法來構造一個底層存放的table數組。如:

public LinkedHashMap(int initialCapacity, float loadFactor) {  
    super(initialCapacity, loadFactor);  
    accessOrder = false;  
}

   HashMap中的相關構造方法:

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);  
  
    // Find a power of 2 >= initialCapacity  
    int capacity = 1;  
    while (capacity < initialCapacity)  
        capacity <<= 1;  
  
    this.loadFactor = loadFactor;  
    threshold = (int)(capacity * loadFactor);  
    table = new Entry[capacity];  
    init();  
}  

  我們已經知道LinkedHashMap的Entry元素繼承HashMap的Entry,提供了雙向鏈表的功能。在上述HashMap的構造器中,最后會調用init()方法,進行相關的初始化,這個方法在HashMap的實現中並無意義,只是提供給子類實現相關的初始化調用。
   LinkedHashMap重寫了init()方法,在調用父類的構造方法完成構造后,進一步實現了對其元素Entry的初始化操作。

 void init() {
        header = new Entry<>(-1, null, null, null);
        header.before = header.after = header;
    }

   LinkedHashMap並未重寫父類HashMap的put方法,而是重寫了父類HashMap的put方法調用的子方法void recordAccess(HashMap m)   ,void addEntry(int hash, K key, V value, int bucketIndex) 和void createEntry(int hash, K key, V value, int bucketIndex),提供了自己特有的雙向鏈接列表的實現。

  HashMap 的put 方法:

  

public V put(K key, V value) {  
        if (key == null)  
            return putForNullKey(value);  
        int hash = hash(key.hashCode());  
        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;  
    }  

  重寫方法:

void recordAccess(HashMap<K,V> m) {  
            LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;  
            if (lm.accessOrder) {  
                lm.modCount++;  
                remove();  
                addBefore(lm.header);  
            }  
        }  

void addEntry(int hash, K key, V value, int bucketIndex) {  
    // 調用create方法,將新元素以雙向鏈表的的形式加入到映射中。  
    createEntry(hash, key, value, bucketIndex);  
  
    // 刪除最近最少使用元素的策略定義  
    Entry<K,V> eldest = header.after;  
    if (removeEldestEntry(eldest)) {  
        removeEntryForKey(eldest.key);  
    } else {  
        if (size >= threshold)  
            resize(2 * table.length);  
    }  
}  

void createEntry(int hash, K key, V value, int bucketIndex) {  
    HashMap.Entry<K,V> old = table[bucketIndex];  
    Entry<K,V> e = new Entry<K,V>(hash, key, value, old);  
    table[bucketIndex] = e;  
    // 調用元素的addBrefore方法,將元素加入到哈希、雙向鏈接列表。  
    e.addBefore(header);  
    size++;  
}  

private void addBefore(Entry<K,V> existingEntry) {  
    after  = existingEntry;  
    before = existingEntry.before;  
    before.after = this;  
    after.before = this;  
}  

6、HashTable 和HashMap 的區別:

  HashTable 和HashMap 大致相同,我們這部在累述,在這只說下他們的區別:

public class Hashtable<K,V>
    extends Dictionary<K,V>
    implements Map<K,V>, Cloneable, java.io.Serializable 

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

 Hashtable 繼承自Dictionary HashMap 繼承自AbstractMap。

 HashTable 的put 方法:

public synchronized V put(K key, V value) {
        // Make sure the value is not null
        if (value == null) {
            throw new NullPointerException();
        }

        // Makes sure the key is not already in the hashtable.
        Entry tab[] = table;
        int hash = hash(key);
        int index = (hash & 0x7FFFFFFF) % tab.length;
        for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
            if ((e.hash == hash) && e.key.equals(key)) {
                V old = e.value;
                e.value = value;
                return old;
            }
        }

        modCount++;
        if (count >= threshold) {
            // Rehash the table if the threshold is exceeded
            rehash();

            tab = table;
            hash = hash(key);
            index = (hash & 0x7FFFFFFF) % tab.length;
        }

        // Creates the new entry.
        Entry<K,V> e = tab[index];
        tab[index] = new Entry<>(hash, key, value, e);
        count++;
        return null;
    }

  注意1 方法是同步的
  注意2 方法不允許value==null
  注意3 方法調用了key的hashCode方法,如果key==null,會拋出空指針異常 HashMap的put方法如下

  HashMap 的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 方法是非同步的
  注意2 方法允許key==null
  注意3 方法並沒有對value進行任何調用,所以允許為null


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM