java HashMap的实现

作为java程序员,hashmap是最常用的类之一了,记得以前面试也经常被问到,记得我当时的回答是把key做hash直接映射到内存地址,hash冲突使用链表解决。今天看了源码记录一下,有个技巧是,HashMap直接debug是看不到很多值的,所以自己把HashMap和AbstractMap拷贝重命名下,就可以使用debug了。

首先理解下以下几点,最后以看下put函数就能明白了。

1 DEFAULT_LOAD_FACTOR = 0.75f; 默认的加载因数,就是扩不扩容什么时刻扩容主要由这个参数决定。
2 transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE; 底层的那个数组默认是16,可扩容,大小必须是2的平方,存储的是Entry。
3 最重要的hash函数:为了让所有的key,不论是什么类型,都能映射为0-table.length 之间的一个整数

/**
     * Retrieve object hash code and applies a supplemental hash function to the
     * result hash, which defends against poor quality hash functions.  This is
     * critical because HashMap uses power-of-two length hash tables, that
     * otherwise encounter collisions for hashCodes that do not differ
     * in lower bits. Note: Null keys always map to hash 0, thus index 0.
     */
    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).
        //这里会用到所有的高位和地位,为了让任何一位变化都能引起hash变化,是hash散列更均衡
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }

    /**
     * Returns index for hash code h.
     */
    static int indexFor(int h, int length) {
        // 这里使用了一个技巧,相当于h%length取模,但是位运算效率会高些。
        // assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";
        return h & (length-1);
    }

4 Entry不仅是一个键值对,还是一个链表,测试存储100个key,使用1-50和随机字符串作为键值,hash冲突是很多的。


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

最后看下put操作:

    public V put(K key, V value) {
        //检查数组保证已经初始化
        if (table == EMPTY_TABLE) {
            inflateTable(threshold);
        }
        //对于null健单独存储并替换
        if (key == null)
            return putForNullKey(value);
        int hash = hash(key);
        //计算出在table中的索引
        int i = indexFor(hash, table.length);
        //检测如果hash冲突,是否key已经存在,存在则替换value
        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;
    }
 void addEntry(int hash, K key, V value, int bucketIndex) {
         //大小大于length*loadfactor 并当前索引(hahs冲突)被占用时才扩容
        if ((size >= threshold) && (null != table[bucketIndex])) {
            //扩容会全部rehash计算,比较耗费时间,所以如果知道一开始hashmap很大,可以设置初始化大一些来避免rehash
            resize(2 * table.length);
            hash = (null != key) ? hash(key) : 0;
            bucketIndex = indexFor(hash, table.length);
        }
        createEntry(hash, key, value, bucketIndex);
    }
      void createEntry(int hash, K key, V value, int bucketIndex) {
        //如果当前索引已被占用时,会先取出来,并将新建的table[bucketIndex] 的next指向e,构成链表。get时只要依次比较key是否相等即可找到对应的Entry。
        Entry<K,V> e = table[bucketIndex];
        table[bucketIndex] = new Entry<>(hash, key, value, e);
        size++;
    }

其实应该还有很多细节,感觉也不需要细看,理解原理和数据结构就可以了,一些技巧当然可以借鉴,然后就是有助于以后更好的使用hashmap来写代码。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱美事爱生活

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值