HashMap源码学习

1 篇文章 0 订阅

基础变量

/**
     * The default initial capacity - MUST be a power of two.
     */
    static final int DEFAULT_INITIAL_CAPACITY = 16;
    /**
     * The maximum capacity, used if a higher value is implicitly specified
     * by either of the constructors with arguments.
     * MUST be a power of two <= 1<<30.
    */
   static final int MAXIMUM_CAPACITY = 1 << 30;

    /**
     * The load factor used when none specified in constructor.
     */
    static final float DEFAULT_LOAD_FACTOR = 0.75f;

    /**
     * The table, resized as necessary. Length MUST Always be a power of two.
     */
    transient Entry[] table;

HashMap默认的初始容量为16,同时还强调了容量必须是2的幂次方
最大容量为1<<30。默认的装载因子 为0.75,这个下面再说。
其主体是一个Entry数组table,当需要时会重新分配大小,但是长度依然必须是2的幂次方

装载因子

装载因子=实际放入的元素/HashMap的总容量。
为了避免元素冲突,容量都会比放入的元素大,因此越小的装载因子意味着元素发生碰撞的概率越低,但是也意味着浪费空间的概率会越高。

容量:2的幂次方

HashMap的容量从来都是2的幂次方,虽然插入的数量/装载因子 得到的数字可能不是2的幂次方,但是该容量在申请的时候从来都会变成2的幂次方,这是有原因的。

搜寻位置:哈希与再哈希


    public V put(K key, V value) {
        if (key == null)
            return putForNullKey(value);
        int hash = hash(key.hashCode());
        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;
    }

首先判断key是否为空,hashMap是支持放入null作为key的。
其次,对key的hashCode进行再哈希得到hash。

为何需要进行再哈希呢?事关如下这个函数:indexFor。

    /**
     * Returns index for hash code h.
     */
    static int indexFor(int h, int length) {
        return h & (length-1);
    }

这个函数没有什么特殊,仅仅是使用了位运算来代替求余运算。得到的余数对应着元素的位置。
但是这有个问题:由于length是2的幂次方,length-1就成为了除了最高位为0,其余位全都为1的值,此时如果单纯的通过key的hash进行计算,如果hash的对应低位没变(这很有可能,比如length-1=0111,而hash为1111,10111,11111,110111等等),由于取余结果仅仅跟这些低位有关,而这些低位的值从来没有为了区分整个key.hashCode而做过优化,他们无法代表整个key.hashCode做出区分,因此会导致冲突的高发率
因此,put方法中在indexFor之前,对原有的key.hashCode进行再次hash,使得再哈希结果中的1尽量均匀而且随机的分布在所有数位上,这样求余之后得到的结果碰撞率就减小了。
再哈希方法如下:

      static int hash(int h) {
        // 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);
    }

这就是hashMap容量为何要做到2的幂次方,同时,hashMap的put方法为何要再次hash的意义。
如果还是没有弄懂这么做的意义的话,请仔细琢磨加黑的语句。

对该语句的理解:虽然key.hashCode都是不相同的,这足以区分各个key,但是,他们的不同仅仅是表现在了hashCode的除去低length-1位的高位中,因为在上述的例子中:比如length-1=0111,而hashCode为1111,10111,11111,110111等等,那么他们的区分度并不在末尾的三位上,而是在高位上,因为末尾三位跟length-1相与结果是一样的,这就产生了冲突。因此,再哈希的意思就是尽量让这末尾的三位中也能体现出各自hashCode的不同,这就是意义所在。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值