美文网首页
jdk源码分析之HashMap

jdk源码分析之HashMap

作者: shoulda | 来源:发表于2018-05-19 09:21 被阅读0次

    HashMap的底层数据

    HashMap底层采用数组加链表的数据结构存储键值对
    HashMap根据key的哈希值转换为数组的下标将键值对存入数组中,数组的元素是一个链表,冲突的key放置在数组的同一个位置,使用链表将冲突的数据链接起来
    底层结构如下:

     /**
         * An empty table instance to share when the table is not inflated.
         */
        static final Entry<?,?>[] EMPTY_TABLE = {};
    
        /**
         * The table, resized as necessary. Length MUST Always be a power of two.
         */
        transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
    

    可见数组存储的元素是Entry,而Entry表示链表的节点存储了key,value,key的hash值,指向下一个节点的指针域next,Entry数据hashCode()方法是通过key的hash值和value的hash值异或得到的。equals方法判断两个entry相等的标准是key相等且value相等。重写equals方法必须重写hashCode方法,满足两个对象相等,则这两个对象的hash值必须相等。

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

    根据hash值计算桶中的位置

    static int indexFor(int h, int length) {
            // assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";
            return h & (length-1);
        }
    

    数组的长度是2的整数幂,h$(length-1)等价于

    h % length
    

    通过位运算来实现取余运算,比较节省cpu运行时间

    根据key获取Entry

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

    首先判断size是否为0,size为0表示HashMap中没有键值对,此时通过key获取value必然返回null,然后判断key是否为null,null作为key,其hash值为0,null key在entry数组中的位置为index=0处
    key不为null的话通过indexFor(hash, table.length)计算桶的位置 。
    根据桶的位置获取链表头,然后遍历链表获取所求的entry

    Entry<K,V> e = table[indexFor(hash, table.length)];
    

    此时的e表示链表的头部,此链表的所有节点的key的hash值相同,但是key不同,因此扫描链表,获取对应的key的entry,然后返回这个ertry.

    键key为null时候的特殊处理

    HashMap允许null的键和null的值,键为null的entry都是放在entry数组索引为0地方

       private V getForNullKey() {
            if (size == 0) {
                return null;
            }
            for (Entry<K,V> e = table[0]; e != null; e = e.next) {
                if (e.key == null)
                    return e.value;
            }
            return null;
        }
        如果size==0,则value必然为null,直接返回null
        否则通过Entry<K,V> e = table[0]获取链表头部
        通过e.key == null找到key为null的entry,返回此entry的value
    
    

    HashMap允许null的键和null的值,键为null的entry都放在entry数组索引为0地方,即下标为0的桶内(也有可能其他非null键hash值刚好为0,因此需要扫描比较)

    通过键获取值

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

    分类处理,key为null通过getForNullKey()获取对应的value
    否则通过getEntry(key)获取对应的entry,通过entry获取value

    向entry链表中添加一个新的entry

    /**
         * Adds a new entry with the specified key, value and hash code to
         * the specified bucket.  It is the responsibility of this
         * method to resize the table if appropriate.
         *
         * Subclass overrides this to alter the behavior of put method.
         */
        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);
        }
    

    首先判断size >= threshold,如果存储的键值对数量size大于阈值threshold的话,需要扩容,然后rehash,重新计算hash值和桶的位置
    然后调用createEntry把entry添加到链表头

     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删除数据

    根据key的hash值找到桶,遍历桶中的链表,找到对应的key的entry,删除。删除的办法就是entry的前序节点的后继指针直接指向entry节点的后继节点

        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)))) {
                    modCount++;
                    size--;
                    if (prev == e)
                        table[i] = next;
                    else
                        prev.next = next;
                    e.recordRemoval(this);
                    return e;
                }
                prev = e;
                e = next;
            }
            return e;
        }
    

    如果size==0,HashMap中没有保存任何键值对,对任何key取value均返回null
    否则:
    计算key的hash值,key==null设置key的hash值为0
    否则通过hash(key)计算hash值
    然后根据key的hash值计算桶的位置

    判断是否包含某个value

    这篇文章介绍的十分详细:http://www.importnew.com/28263.html

    相关文章

      网友评论

          本文标题:jdk源码分析之HashMap

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