java HashMap的实现

    xiaoxiao2021-03-25  77

    作为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来写代码。

    转载请注明原文地址: https://ju.6miu.com/read-14593.html

    最新回复(0)