• HashMap相关源码阅读


    Map提供了将对象映射到其他对象的能力

    看一下HashMap的put方法是如何存储K,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;
        }

    第一句话首先看到table这个变量,看一下table的定义:

    transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;

    可以看出table是一个存储Entry<K,V>的数组,看一下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) {
            }
        }

    可以看到,Entry中有四个成员变量:一个key,一个value,一个hash值,以及另一个Entry

    大致浏览程序可以发现Map的数据结构其实是一个数组,而我们放入的数据在数组的哪个位置,不是按照顺序的,而是根据key值计算出一个hash值,这个值决定了插入的键值对在数组中的位置,这样当我们进行查找时,可以根据key值计算的hash值快速定位到在数组的位置,快速得到key对应的value值。

    但是计算hash值会有一个问题,就是不同的key值可能计算出相同的hash值,那么数组的一个位置怎么存储两个值呢,这时我们看到的Entry类中存储的“Entry<K,V> next“就起到作用了,它可以把具有相同hash值的的Entry像链表一样链接起来,这样问题就解决了。

    所以我们看到put中的这一段代码:

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

    就是在hash值相同及旧的key比较相同时,替换旧的Value值,那么hash值相同但是key值不同时如何存储呢,继续看下面的addEntry(hash, key, value, i)方法做了什么:

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

    其中resize是对hashMap进行扩容,并进行rehash操作,下面的createEntry方法:

    void createEntry(int hash, K key, V value, int bucketIndex) {
            Entry<K,V> e = table[bucketIndex];//取出原有的Entry
            table[bucketIndex] = new Entry<>(hash, key, value, e);//直接调用构造函数,将原有的Entry链到新的Entry后
            size++;
        }

    这样put方法就大概说了一下,很简单,复杂的关于rehash的问题下次再写。。

  • 相关阅读:
    搭建企业级Docker Registry -- Harbor
    搭建私服-docker registry
    CentOS 7.2修改网卡名称
    tomcat错误日志监控脚本
    Openresty+Lua+Redis灰度发布
    Jenkins权限控制-Role Strategy Plugin插件使用
    Rsyslog日志服务搭建
    awk4.0对数组value排序
    Spring-IOC 在非 web 环境下优雅关闭容器
    Spring-IOC bean 生命周期之 Lifecycle 钩子
  • 原文地址:https://www.cnblogs.com/qilong853/p/6495188.html
Copyright © 2020-2023  润新知