美文网首页
HashMap底层实现

HashMap底层实现

作者: 律动的时间线 | 来源:发表于2019-03-21 14:26 被阅读0次

    Map作为键值对储存的集合,其实现类包括HashMap,HashTable等,这里展示HashMap的底层原理。

     static class Node<K,V> implements Map.Entry<K,V> {
            final int hash;
            final K key;
            V value;
            Node<K,V> next;
    
            Node(int hash, K key, V value, Node<K,V> next) {
                this.hash = hash;
                this.key = key;
                this.value = value;
                this.next = next;
            }
    
            public final K getKey()        { return key; }
            public final V getValue()      { return value; }
            public final String toString() { return key + "=" + value; }
    
            public final int hashCode() {
                return Objects.hashCode(key) ^ Objects.hashCode(value);
            }
    
            public final V setValue(V newValue) {
                V oldValue = value;
                value = newValue;
                return oldValue;
            }
    

    首先是在HashMap类中定义了一个Node 类,用于储存键值对,该类实现了Map.Entry<K,V>作为储存键值对的结构。

    transient Node<K,V>[] table;
    
        /**
         * Holds cached entrySet(). Note that AbstractMap fields are used
         * for keySet() and values().
         */
        transient Set<Map.Entry<K,V>> entrySet;
    
        /**
         * The number of key-value mappings contained in this map.
         */
        transient int size;
    
        /**
         * The number of times this HashMap has been structurally modified
         * Structural modifications are those that change the number of mappings in
         * the HashMap or otherwise modify its internal structure (e.g.,
         * rehash).  This field is used to make iterators on Collection-views of
         * the HashMap fail-fast.  (See ConcurrentModificationException).
         */
        transient int modCount;
    
    

    定义了包含Node类的数组和一个Set类,数组是作为哈希表储存数组,那么Set类的作用是什么呢?

    final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
            int s = m.size();
            if (s > 0) {
                if (table == null) { // pre-size
                    float ft = ((float)s / loadFactor) + 1.0F;
                    int t = ((ft < (float)MAXIMUM_CAPACITY) ?
                             (int)ft : MAXIMUM_CAPACITY);
                    if (t > threshold)
                        threshold = tableSizeFor(t);
                }
                else if (s > threshold)
                    resize();
                for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
                    K key = e.getKey();
                    V value = e.getValue();
                    putVal(hash(key), key, value, false, evict);
                }
            }
        }
    

    在上述数组中出现了m.entrySet()方法,接着追溯

    public Set<Map.Entry<K,V>> entrySet() {
            Set<Map.Entry<K,V>> es;
            return (es = entrySet) == null ? (entrySet = new EntrySet()) : es;
        }
    

    可以看到m.entrySet()的作用就是创建entrySet = new EntrySet()实例。
    那么EntrySet又是什么类呢?

    final class EntrySet extends AbstractSet<Map.Entry<K,V>> {
            public final int size()                 { return size; }
            public final void clear()               { HashMap.this.clear(); }
            public final Iterator<Map.Entry<K,V>> iterator() {
                return new EntryIterator();
            }
            public final boolean contains(Object o) {
                if (!(o instanceof Map.Entry))
                    return false;
                Map.Entry<?,?> e = (Map.Entry<?,?>) o;
                Object key = e.getKey();
                Node<K,V> candidate = getNode(hash(key), key);
                return candidate != null && candidate.equals(e);
            }
            public final boolean remove(Object o) {
                if (o instanceof Map.Entry) {
                    Map.Entry<?,?> e = (Map.Entry<?,?>) o;
                    Object key = e.getKey();
                    Object value = e.getValue();
                    return removeNode(hash(key), key, value, true, true) != null;
                }
                return false;
            }
    

    EntrySet类的作用就是实现抽象set集合,其中Map.Entry<K,V>就是返回键对应的这个

    interface Entry<K,V> {
            /**
             * Returns the key corresponding to this entry.
             *
             * @return the key corresponding to this entry
             * @throws IllegalStateException implementations may, but are not
             *         required to, throw this exception if the entry has been
             *         removed from the backing map.
             */
            K getKey();
    

    在源码中可以看到,EntrySet是内部类并且没有值域,也就是说调用entrySet()方法不会返回真正意义上的所有Map.Entey实例的Set对象,那么为什么要使用这个EnteySet类呢?

    final class EntrySet extends AbstractSet<Map.Entry<K,V>> {
            public final int size()                 { return size; }
            public final void clear()               { HashMap.this.clear(); }
            public final Iterator<Map.Entry<K,V>> iterator() {
                return new EntryIterator();
            }
    

    可以看到里面存在new EntryIterator()返回迭代器实例,这才是使用EntrySet类的原因,从抽象上可以理解为迭代的过程和Set迭代一致,但是深入内部的实现原理,可以看到EntryIterator基本就是调用HashIterator

     final class EntryIterator extends HashIterator
            implements Iterator<Map.Entry<K,V>> {
            public final Map.Entry<K,V> next() { return nextNode(); }
        }
    
    abstract class HashIterator {
            Node<K,V> next;        // next entry to return
            Node<K,V> current;     // current entry
            int expectedModCount;  // for fast-fail
            int index;             // current slot
    
            HashIterator() {
                expectedModCount = modCount;
                Node<K,V>[] t = table;
                current = next = null;
                index = 0;
                if (t != null && size > 0) { // advance to first entry
                    do {} while (index < t.length && (next = t[index++]) == null);
                }
            }
    
            public final boolean hasNext() {
                return next != null;
            }
    
            final Node<K,V> nextNode() {
                Node<K,V>[] t;
                Node<K,V> e = next;
                if (modCount != expectedModCount)
                    throw new ConcurrentModificationException();
                if (e == null)
                    throw new NoSuchElementException();
                if ((next = (current = e).next) == null && (t = table) != null) {
                    do {} while (index < t.length && (next = t[index++]) == null);
                }
                return e;
            }
    

    HashIterator的抽象类已经实现了针对哈希链表模式的遍历,这是针对HashMap实现类的遍历实现,与AbstracSet的遍历并没有太多的关系,那么为什么EntrySet要实现AbstracSet类呢,追溯回这个类

    public abstract class AbstractSet<E> extends AbstractCollection<E> implements Set<E> {
        /**
         * Sole constructor.  (For invocation by subclass constructors, typically
         * implicit.)
         */
        protected AbstractSet() {
        }
    
        // Comparison and hashing
    
        /**
         * Compares the specified object with this set for equality.  Returns
         * <tt>true</tt> if the given object is also a set, the two sets have
         * the same size, and every member of the given set is contained in
         * this set.  This ensures that the <tt>equals</tt> method works
         * properly across different implementations of the <tt>Set</tt>
         * interface.<p>
         *
         * This implementation first checks if the specified object is this
         * set; if so it returns <tt>true</tt>.  Then, it checks if the
         * specified object is a set whose size is identical to the size of
         * this set; if not, it returns false.  If so, it returns
         * <tt>containsAll((Collection) o)</tt>.
         *
         * @param o object to be compared for equality with this set
         * @return <tt>true</tt> if the specified object is equal to this set
         */
        public boolean equals(Object o) {
            if (o == this)
                return true;
    
            if (!(o instanceof Set))
                return false;
            Collection<?> c = (Collection<?>) o;
            if (c.size() != size())
                return false;
            try {
                return containsAll(c);
            } catch (ClassCastException unused)   {
                return false;
            } catch (NullPointerException unused) {
                return false;
            }
        }
    
        /**
         * Returns the hash code value for this set.  The hash code of a set is
         * defined to be the sum of the hash codes of the elements in the set,
         * where the hash code of a <tt>null</tt> element is defined to be zero.
         * This ensures that <tt>s1.equals(s2)</tt> implies that
         * <tt>s1.hashCode()==s2.hashCode()</tt> for any two sets <tt>s1</tt>
         * and <tt>s2</tt>, as required by the general contract of
         * {@link Object#hashCode}.
         *
         * <p>This implementation iterates over the set, calling the
         * <tt>hashCode</tt> method on each element in the set, and adding up
         * the results.
         *
         * @return the hash code value for this set
         * @see Object#equals(Object)
         * @see Set#equals(Object)
         */
        public int hashCode() {
            int h = 0;
            Iterator<E> i = iterator();
            while (i.hasNext()) {
                E obj = i.next();
                if (obj != null)
                    h += obj.hashCode();
            }
            return h;
        }
    
    

    AbstracSet类仅仅实现了两个方法,hashCode()equals而正好这也是Map.EntrySet需要的,因为这个类内部实现了contains()remove()方法,这两个方法都需要使用hash值定位以及key值查找同一数组下标的链表中符合要求的key的节点。

    另外,从源码可以看出来,Map插入null值key的实现方式和网上的putForNullKey()不同,这个方法已经没有了,取而代之解释null的hash值为0,也就是在下标为0处保存key=null的键值对,而map同样不允许key重复,所有当多个null的key插入时会不断覆盖之前的value,结果就是只能保存最近一个key为null的键值对的值

    相关文章

      网友评论

          本文标题:HashMap底层实现

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