java HashMap原理分析

HashMap的size指mapping个数,使用Entry[]数据实现,每个Entry有个指向Entry对象的next变量,形成链表。Put()时首先通过hash(key.hashcode())获取key的hash,将key.hashcode()再hash加入了高位运算,防止低位相同时引起的hash冲突。

final int hash(Object k) {
       int h = 0;
       if (useAltHashing) {
            if (k instanceofString) {
                return sun.misc.Hashing.stringHash32((String)k);
            }
            h = hashSeed;
       }
 
       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);
   }

Java8中的hash():只进行了一次高位运算。

static final int hash(Object key){
       int h;
       return (key == null) ? 0 : (h = key.hashCode()) ^ (h>>> 16);
 }


然后通过indexof(hash)获取key在Entry[]中的位置

static int indexFor(int h, intlength) {
       return h & (length-1);
   }

这里使用数组长度-1与hash相与,等价于hash%数组长度,与运算快于求模运算。因为length等于2的n次方幂,这也是为什么hashmap数组长度是2的n次方幂的原因。

找到key在数组中的位置后,然后通过key的equals()查找key应该在链表中的位置

public V put(K key, V value) {
       if (key == null)
            return putForNullKey(value);
       int hash = hash(key);
       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);
                returnoldValue;
            }
        }
 
       modCount++;
       addEntry(hash, key, value, i);
       return null;
   }


如果key已经存在,直接替换掉value值,返回旧的value值;如果链表中不存在相等的key,通过addEntry(hash, key, value, i)添加一个新节点,并且将modCount++;modCount表示HashMap已被结构化修改的次数。结构修改是改变HashMap中的映射数量或者修改其内部结构(例如rehash)的修改次数。此字段用于使HashMap的集合视图上的迭代器失效。 (请参阅ConcurrentModificationException)。

 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);
       }
 
       createEntry(hash, key, value, bucketIndex);
  }


如果当前size >= threshold&& null != table[bucketIndex],将调用resize(newCapacity)扩容。

如果不是,直接createEntry()

void createEntry(int hash, K key, Vvalue, int bucketIndex) {
       Entry<K,V> e = table[bucketIndex];
       table[bucketIndex] = new Entry<>(hash, key, value, e);
       size++;
   }

 void resize(int newCapacity) {
       Entry[] oldTable = table;
       int oldCapacity = oldTable.length;
       if (oldCapacity == MAXIMUM_CAPACITY) {
            threshold =Integer.MAX_VALUE;
            return;
        }
 
       Entry[] newTable = new Entry[newCapacity];
       boolean oldAltHashing = useAltHashing;
       useAltHashing |= sun.misc.VM.isBooted() &&
                (newCapacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
       boolean rehash = oldAltHashing ^ useAltHashing;
       transfer(newTable, rehash);
        table = newTable;
       threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY+ 1);
   }

void transfer(Entry[] newTable, booleanrehash) {
       int newCapacity = newTable.length;
       for (Entry<K,V> e : table) {
            while(null!= e) {
                Entry<K,V>next = e.next;
                if(rehash) {
                    e.hash= null == e.key ? 0 : hash(e.key);
                }
                inti = indexFor(e.hash, newCapacity);
                e.next= newTable[i];//标记【1】
                newTable[i]= e;
                e = next;
            }
        }
   }

注释标记[1]处,将newTable[i]的引用赋给了e.next,也就是使用了单链表的头插入方式,同一位置上新元素总会被放在链表的头部位置;这样先放在一个索引上的元素终会被放到Entry链的尾部(如果发生了hash冲突的话);

loadFactory越大,扩容次数少,对空间利用更充分,然而链表太长,查找效率变低;

loadFactory越小,扩容次数多,数据之间就会很松散,对空间造成严重浪费。

因此Java8对hashMap进行了优化,当一条链表上的元素个数超过了8个,链表就转换为红黑树。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值