HashMap源码--(三)put方法

来源:互联网 发布:sql日月年转年月日 编辑:程序博客网 时间:2024/06/05 02:17

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,进行覆盖。