jdk1.7的HashMap底层原理

简介

  1. 基于哈希表的 Map 接口的实现
  2. HashMap是线程不安全的,在多线程下可能出现问题,而HashTable是线程安全的。
  3. HashMap 的实例有两个参数影响其性能:初始容量 和加载因子。将在下文介绍。

在这里插入图片描述

源码分析

Entry类型存储

//用于存储Node节点,由上图所示
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;
    }

    public final K getKey() {
        return key;
    }

    public final V getValue() {
        return value;
    }

    public final V setValue(V newValue) {
        V oldValue = value;
        value = newValue;
        return oldValue;
    }
	//重写equals方法
    public final boolean equals(Object o) {
        if (!(o instanceof Map.Entry))
            return false;
        Map.Entry e = (Map.Entry)o;
        Object k1 = getKey();
        Object k2 = e.getKey();
        if (k1 == k2 || (k1 != null && k1.equals(k2))) {
            Object v1 = getValue();
            Object v2 = e.getValue();
            if (v1 == v2 || (v1 != null && v1.equals(v2)))
                return true;
        }
        return false;
    }
}

存入键值对

public V put(K key, V value) {
    //数组为空,初始化数组
    if (table == EMPTY_TABLE) {
        inflateTable(threshold);
    }
    // 如果key为null的情况,将新增键值对插入table[0]的位置
    if (key == null)
        return putForNullKey(value);
    //获取哈希值
    int hash = hash(key);
    //获取数组下标
    int i = indexFor(hash, table.length);
    //遍历对应地址的链表,即table[i]位置的链表
    for (Entry<K,V> e = table[i]; e != null; e = e.next) {
        Object k;
        //如果哈希值相同且对应的key相同,则更新value值并返回
        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;
}

填充(初始化)数组

private void inflateTable(int toSize) {
    // Find a power of 2 >= toSize
    // 将数组容量变为-->大于传入数组容量的最小的2的整次幂,若传入容量为15,则新的容量为16
    int capacity = roundUpToPowerOf2(toSize);
    //临界值= 数组总容量*装载因子
    threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
    //创建新数组
    table = new Entry[capacity];
    //判断是否需要再次获取hash值
    initHashSeedAsNeeded(capacity);
}

确定扩容后容量大小

//例如:number=7,则经过roundUpToPowerOf2后得到的capacity值就为8;number=9,计算后的值为16;number=8,则计算后的值不变为8
//保证每次扩容后都为2的幂次方
private static int roundUpToPowerOf2(int number) {
        // assert number >= 0 : "number must be non-negative";
        return number >= MAXIMUM_CAPACITY
                ? MAXIMUM_CAPACITY
                : (number > 1) ? Integer.highestOneBit((number - 1) << 1) : 1;
    }

Key为空,放在table[0]

private V putForNullKey(V value) {
		//遍历table[0]的链表,如果存在key相同的则覆盖
        for (Entry<K,V> e = table[0]; e != null; e = e.next) {
            if (e.key == null) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
        modCount++;
        //哈希值为0,如果找不到对应的key,则插入链表
        addEntry(0, null, value, 0);
        return null;
    }

插入链表

void addEntry(int hash, K key, V value, int bucketIndex) {
		//在插入新的键值对之前,先判断是否需要扩容
        if ((size >= threshold) && (null != table[bucketIndex])) {
        	//如果当前size超过临界值threshold,进行扩容处理
            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) {
        Entry<K,V> e = table[bucketIndex];
        table[bucketIndex] = new Entry<>(hash, key, value, e);
        //插入键值对后size+1
        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];
        //转移函数
        transfer(newTable, initHashSeedAsNeeded(newCapacity));
        //生成新的table
        table = newTable;
        //更新临界值
        threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
    }

转移函数

void transfer(Entry[] newTable, boolean rehash) {
        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);
                }
                //重新获取地址索引
                int i = indexFor(e.hash, newCapacity);
                //采用头插法,在这里,将旧的链表转移后的会变成倒序
                e.next = newTable[i];
                newTable[i] = e;
                e = next;
            }
        }
    }

获取hash值

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

		//hasCode进行右移和和异或运算,使得生成的哈希值更具随机性和散列性
        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);
    }

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值