zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    HTML总结
    js五角星评分特效
    正则表达式
    C#文件路径的写法
    vs2010发布网站
    INI文件阅读器
    .net读取xml文件中文乱码问题解决办法
    js利用定时器动态改变层大小
    c#中ref和out的用法
    分享一篇关于C#对文件操作的日志,方法很全
  • 原文地址:https://www.cnblogs.com/gaohuiqian/p/5227014.html
Copyright © 2011-2022 走看看