HashMap源码–(三)put方法

Map内部的数据结构是以key-value键值对的方式存数据。key和value都可以为空。
Map有很多子类,HashMap、LinkedListMap、TreeMap等, HashMap是比较常用的,它的存取速度快,是基于哈希表的Map接口实现。存取数据时是根据哈希算法计算数据存在位置,在相同哈希值计算的位置存放的数据结构是链表。添加元素使用方法put方法,如下。

/**
     * 将指定的value和指定的key映射到map中。
     * 如果map中包含这个key,则替换。
     *
     * @param key 一个对象,例如字符,用于映射指定的值
     * @param value map中存的值,根据映射的key进行获取的值
     * @return 如果map中存在key,则返回被替换的值;如果key不存在
     * 则返回null。根据返回值key判断map中是否存在。
     */
    public V put(K key, V value) {
        //key为null时,添加或替换value
        if (key == null)
            return putForNullKey(value);
        //获取哈希值
        int hash = hash(key.hashCode());
        //根据哈希值和Entry[] table数组的长度计算出value存放的位置
        int i = indexFor(hash, table.length);
        //根据数组的位置找到链表的表头
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            //根据哈希值和key查找Entry
            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;
            }
        }
        //操作次数加1
        modCount++;
        //Entry数组为找到key,添加数据
        addEntry(hash, key, value, i);
        return null;
    }

put方法添加或修改数据时,key为null时的添加方法为:

/**
     * key为null时添加值
     */
    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的值决定了值存放在Entry数组的位置,key为null时,存放在Entry数组下标为0的位置;key不为null时,要根据哈希算法和数组的长度计算存放位置,存放位置算法如下:

/**
    *计算hash值
    */
    static int hash(int h) {
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }

    /**
     * 根据数组的长度,计算哈希值h在数组的位置
     */
    static int indexFor(int h, int length) {
        return h & (length-1);
    }

相同的key用hash(int h)方法计算出相同的值。hash方法采用位移算法,这个算法采用高位计算,防止低位不变,高位变化时,hash值冲突问题。
indexFor方法是计算hash值h在数组存放的位置。该方法运用的很巧妙,当length的长度为2的幂指数时,h & (length-1)计算公式相当于对length取模,但&的效率比%高。

假设数组长度分别为15和16,优化后的hash码分别为8和9,那么&运算后的结果如下:

h & (table.length-1)   hash    table.length-1

   8 & (15-1):           0100   &    1110  =    0100
   9 & (15-1):           0101   &    1110  =    0100

   ---------------------------------------------------

   8 & (16-1):           0100   &    1111   =    010
   9 & (16-1):           0101   &    1111   =    0101

可以看出当长度为15时,8和9对应的值是相同的,长度为16时,值则不相同。可见HashMap的长度为2的幂指数的巧妙,且根据长度获取位置也很高效。

根据数组位置i获取Entry对象,
for (Entry<K,V> e = table[i]; e != null; e = e.next)
这个对象Entry e是链表的表头,它的next指向链表的下一个对象。
if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
在链表中匹配hash值和key,进行覆盖。