美文网首页
HashMap学习

HashMap学习

作者: 进击的勇士 | 来源:发表于2017-03-01 20:18 被阅读0次

    概述

    1. 线程非安全,并且允许key与value都为null值,HashTable与之相反,为线程安全,key与value都不允许null值。
    2. put、get操作的时间复杂度为O(1)。
    3. 不保证其内部元素的顺序,而且随着时间的推移,同一元素的位置也可能改变(resize的情况)
    4. 遍历其集合视角的时间复杂度与其容量(capacity,槽的个数)和现有元素的大小(entry的个数)成正比
    5. Map m = Collections.synchronizedMap(new HashMap(...)); 通过这种方式可以得到一个线程安全的map。

    数据结构

    哈希表:数组+链表

    markdown-img-paste-20161128182007235.png

    定义

    签名

    public class HashMap<K,V>
        extends AbstractMap<K,V> //AbstractMap类提供Map接口的基本实现
        implements Map<K,V>, Cloneable, Serializable//Map接口定义了键映射到值的规则
    
    

    哈希函数的设计

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

    get操作

      public V get(Object key) {
            if (key == null)
                return getForNullKey();
            Entry<K,V> entry = getEntry(key);
    
            return null == entry ? null : entry.getValue();
        }
    
        private V getForNullKey() {
            if (size == 0) {
                return null;
            }
            // Null keys map to index 0,遍历单向链表
            for (Entry<K,V> e = table[0]; e != null; e = e.next) {
                if (e.key == null)
                    return e.value;
            }
            return null;
        }
    
        final Entry<K,V> getEntry(Object key) {
            // 根据key寻找entry,类似于给下表,在数组中寻找
            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;
                // 相等条件:hash值相等且key相等(==或者equal相等)
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    return e;
            }
            return null;
        }
    

    containsKey操作

      public boolean containsKey(Object key) {
          return getEntry(key) != null;
      }
    

    put操作

      public V put(K key, V value) {
            if (table == EMPTY_TABLE) {
                inflateTable(threshold);
            }
            // key为null时的put操作
            if (key == null)
                return putForNullKey(value);
            // key不为null时put操作:先查看key是否存在,存在则更新
            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;
                }
            }
    
            // 不存在则新加一条entry,modCount++
            modCount++;
            addEntry(hash, key, value, i);
            return null;
        }
    
        /**
         * Offloaded version of put for null keys
         */
        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;
        }
    
        /**
         * This method is used instead of put by constructors and
         * pseudoconstructors (clone, readObject).  It does not resize the table,
         * check for comodification, etc.  It calls createEntry rather than
         * addEntry.
         */
        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<K,V> 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);
        }
    
        private void putAllForCreate(Map<? extends K, ? extends V> m) {
            for (Map.Entry<? extends K, ? extends V> e : m.entrySet())
                putForCreate(e.getKey(), e.getValue());
        }
    
    
    
        // put一个map集合的操作
        public void putAll(Map<? extends K, ? extends V> m) {
            int numKeysToBeAdded = m.size();
            if (numKeysToBeAdded == 0)
                return;
    
            if (table == EMPTY_TABLE) {
                inflateTable((int) Math.max(numKeysToBeAdded * loadFactor, threshold));
            }
    
            /*
             * Expand the map if the map if the number of mappings to be added
             * is greater than or equal to threshold.  This is conservative; the
             * obvious condition is (m.size() + size) >= threshold, but this
             * condition could result in a map with twice the appropriate capacity,
             * if the keys to be added overlap with the keys already in this map.
             * By using the conservative calculation, we subject ourself
             * to at most one extra resize.
             */
            if (numKeysToBeAdded > threshold) {
                int targetCapacity = (int)(numKeysToBeAdded / loadFactor + 1);
                if (targetCapacity > MAXIMUM_CAPACITY)
                    targetCapacity = MAXIMUM_CAPACITY;
                int newCapacity = table.length;
                while (newCapacity < targetCapacity)
                    newCapacity <<= 1;
                if (newCapacity > table.length)
                    resize(newCapacity);
            }
    
            for (Map.Entry<? extends K, ? extends V> e : m.entrySet())
                put(e.getKey(), e.getValue());
        }
    

    resize操作

    
    void resize(int newCapacity) {
        Entry[] oldTable = table;
        int oldCapacity = oldTable.length;
        // capacity已经最大,则将threshold设置为最大值
        if (oldCapacity == MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return;
        }
        // 构造新的数组
        Entry[] newTable = new Entry[newCapacity];
        // 将旧数组中的数据转移到新数组中
        transfer(newTable, initHashSeedAsNeeded(newCapacity));
        table = newTable;
        // 更新threshold
        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;
        // 遍历旧数组当中的每一条entry
        for (Entry<K,V> e : table) {
            while(null != e) {
                Entry<K,V> next = e.next;
                if (rehash) {
                    // 在新数组当中的hash值
                    e.hash = null == e.key ? 0 : hash(e.key);
                }
                // 重新计算hash值后在新数组中的下表
                int i = indexFor(e.hash, newCapacity);
    
                e.next = newTable[i];
                newTable[i] = e;
                e = next;
            }
        }
    }
    

    hash函数h & (length-1)详解
    由于length是2的幂,所以length-1每一位都是1,相当于取余操作,且碰撞的几率小。如果有0,则与的时候可能不同的数会发生哈希碰撞。同时,如果与的时候有0,那么与的结果一定是0,则该位置为1的一些地址始终会用不到,造成空间浪费!

    remove操作

      public V remove(Object key) {
            Entry<K,V> e = removeEntryForKey(key);
            return (e == null ? null : e.value);
        }
    
        /**
         * Removes and returns the entry associated with the specified key
         * in the HashMap.  Returns null if the HashMap contains no mapping
         * for this key.
         */
        final Entry<K,V> removeEntryForKey(Object key) {
            if (size == 0) {
                return null;
            }
            int hash = (key == null) ? 0 : hash(key);
            int i = indexFor(hash, table.length);
            // 该索引的第一个节点
            Entry<K,V> prev = table[i];
            Entry<K,V> e = prev;
    
            while (e != null) {
                Entry<K,V> next = e.next;
                Object k;
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k)))) {
                    // 找到要删除的key
                    modCount++;
                    size--;
                    if (prev == e)
                        // 如果第一个就是要删除的节点,直接将下一个节点放到table[i]
                        table[i] = next;
                    else
                        // 否则将上一个节点的next指向要删除的下一个节点
                        prev.next = next;
                    e.recordRemoval(this);
                    return e;
                }
                // 没有找到,向后循环查找
                prev = e;
                e = next;
            }
    
            return e;
        }
    

    clear操作

        // 将table的每一个位置置为null,注意并不会重置capacity
        public void clear() {
            modCount++;
            Arrays.fill(table, null);
            size = 0;
        }
    
    

    containsKey/containsValue操作

        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;
        }
    
    
        private boolean containsNullValue() {
            Entry[] tab = table;
            for (int i = 0; i < tab.length ; i++)
                for (Entry e = tab[i] ; e != null ; e = e.next)
                    if (e.value == null)
                        return true;
            return false;
        }
    
    

    单项链表entry

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

    相关文章

      网友评论

          本文标题:HashMap学习

          本文链接:https://www.haomeiwen.com/subject/codcgttx.html