JDK1.7中的HashMap源码揭秘

HashMap源码揭秘

1,put方法逐行解释:

public V put(K key, V value) {
        if (table == EMPTY_TABLE) {//table数组是否为空,如果为空(即new的hashMap()没有指定hashMap的大小),
            inflateTable(threshold);//则进行初始化
        }
        if (key == null)//HashMap对象的key可以为空
            return putForNullKey(value);//如果key为空了,采用另一种策略
        int hash = hash(key);//对key的hashCode值进行一次hash()函数,来算出新的hash值,对key.hashCode进行hash函数是为了减少碰撞
        int i = indexFor(hash, table.length);//根据hash值,来算出key对应的数组的下标,常见的方式有取模,但这里不是
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {//如果下标所在的数组位存在,遍历第i个位置上的链表
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {//看链表上是否有要找的key,如果有,则返回老的值
                V oldValue = e.value; //如果链表上已将有了要设置的key,则先取到老的value,进行保存,让新的value赋给key
                e.value = value; //用新的value来覆盖掉老的value
                e.recordAccess(this);//这是个空方法
                return oldValue; //返回老的value
            }
        }

        modCount++;
        addEntry(hash, key, value, i);//前插法,插入元素
        return null;
    }

2.put方法中调用的重要的方法解析:
(1)第25行的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);
        }//if语句和扩容有关

        createEntry(hash, key, value, bucketIndex);//这一步才是插入元素的关键
    }

其中 createEntry(hash, key, value, bucketIndex); 采用头插法,来插入新的k-v键值对方法如下

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

其中 table[bucketIndex] = new Entry<>(hash, key, value, e);分为两步执行
(1), new Entry<>(hash, key, value, e);
    new一个新的Entry 对象,并把它next指向老的头结点所在的那一串链表,
    即让新创建的Entry的next赋值为e。
(2) 让新的头结点去指向新的链表,即让new Entry<>(hash, key, value, e);赋值给 table[bucketIndex]
前插法的过程
前插法过程
why 要选择头插法?
因为尾插法,你还要遍历链表去找到最后一个尾结点,与头插法相比,比较浪费时间
采用头插法的话,只需要让新插入即new出的Entry对象的节点的next指向老的头结点,
让新的头结点指向新的结点

3.indexFor(int n,int length)方法,来取一个 key应该放在数组的那个下标位置,首先index应该小于length-1,然后再考虑如何均匀地分配

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

(1).h是经过hash()函数运算后的hash值),而length是一个2的整数次幂(这和初始化数组时有关)的数,减去1之后,会发现它的最低位全为1
indexFor方法相当于取模操作,1.要保证数组下标不越界,2,要保证尽量的平均,减少冲突,一个数字%(length-1),
(2).例如一个int[10]数组,给定一个数2,让他%(10-1),等于2,就把它放在数组的第二个位置,
(3).也就是说,尽可能的让一个数存放在下标等于它自己的值的位置,又因为任何数和全为1的数相&都为他们本身,
(4).所以 h &(length-1)能够实现数组不会越界,尽可能地实现平均,减少碰撞

4.hash( Object key)函数,主要是算出k的h值,

final int hash(Object k) {
        int h = hashSeed;
        if (0 != h && k instanceof String) {
            return sun.misc.Hashing.stringHash32((String) k);
        }

        h ^= k.hashCode();

        // This function ensures that hashCodes that differ only by
        // constant multiples at each bit position have a bounded
        // number of collisions (approximately 8 at default load factor).
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }

该函数的作用是让高位参与,让k生成的h在进行求数组下标运算的过程中尽可能的使k(让高位参与&运算)均匀地放在数组中,减少hash冲突

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值