HashMap集合底层原理----基础知识

一、HashMap成员变量

/** 初始容量,默认16 =2^4*/
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

/** 最大初始容量,2^30 */
static final int MAXIMUM_CAPACITY = 1 << 30;

/** 负载因子,默认0.75,负载因子越小,hash冲突机率越低 */
static final float DEFAULT_LOAD_FACTOR = 0.75f;

/** 初始化一个Entry的空数组 */
static final Entry<?,?>[] EMPTY_TABLE = {};

/** 将初始化好的空数组赋值给table,table数组是HashMap实际存储数据的地方,并不在EMPTY_TABLE数组中 */
transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;

/** HashMap实际存储的元素个数 */
transient int size;

/** 临界值(HashMap 实际能存储的大小),公式为(threshold = capacity * loadFactor) */
int threshold;

/** 负载因子 */
final float loadFactor;

/** HashMap的结构被修改的次数,用于迭代器 */
transient int modCount;

二、构造方法

HashMap实现了Map接口,继承AbstractMap。其中Map接口定义了键映射到值的规则,而AbstractMap类提供 Map 接口的骨干实现。

/**
     * 构造一个空的HashMap,默认容器初始化大小为16,默认负载因子0.75
*/
  public HashMap() {
		//this(默认初始容量(16),默认负载因子(0.75));
  		this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
  }
  public HashMap(int initialCapacity) {
  		//this(定的初始容量,默认负载因子(0.75));
  		this(initialCapacity, DEFAULT_LOAD_FACTOR);
  }

public HashMap(int initialCapacity, float loadFactor) {
        // 判断设置的容量和负载因子合不合理
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
         初始容量不能 > 最大容量值,HashMap的最大容量值为2^30                                     
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        // 负载因子不能 < 0
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);
        // 设置负载因子,临界值此时为容量大小,后面第一次put时由inflateTable(int toSize)方法计算设置
        this.loadFactor = loadFactor;
        threshold = initialCapacity;
        init();
    }

三、Entry

每次初始化HashMap都会构造一个table数组,而table数组的元素为Entry节点。

static class Entry<K,V> implements Map.Entry<K,V> {
        final K key;
        V value;
        Entry<K,V> next;
        int hash;
}

HashMap也可以说是一个数组链表,HashMap里面有一个非常重要的内部静态类——Entry,这个Entry非常重要,它里面包含了键key,值value,下一个节点next,以及hash值,Entry是HashMap非常重要的一个基础Bean,因为所有的内容都存在Entry里面,HashMap的本质可以理解为 Entry[ ] 数组。

四、put()方法

public V put(K key, V value) {
		if (table == EMPTY_TABLE) { //是否初始化
            inflateTable(threshold);
        }
        //当key为null,调用putForNullKey方法,保存null与table第一个`        位置中,这是HashMap允许为null的原因
        if (key == null)
            return putForNullKey(value);
        //计算key的hash值
        int hash = hash(key.hashCode());                  ------(1)
        //计算key hash 值在 table 数组中的位置
        int i = indexFor(hash, table.length);             ------(2)
        //从i出开始迭代 e,找到 key 保存的位置
        for (Entry<K, V> e = table[i]; e != null; e = e.next) {
            Object k;
            //判断该条链上是否有hash值相同的(key相同)
            //若存在相同,则直接覆盖value,返回旧value
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;    //旧值 = 新值
                e.value = value;
                e.recordAccess(this);
                return oldValue;     //返回旧值
            }
        }
        //修改次数增加1
        modCount++;
        //将key、value添加至i位置处
        addEntry(hash, key, value, i);
        return null;
    }

putForNullKey()方法:

private V putForNullKey(V value) {  
        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++;  
        addEntry(0, null, value, 0);  
        return null;  
    } 

直接去遍历table[0] Entry链表,找出e.key==null的Entry或者没有找到遍历结束
如果找到了e.key等于null的值就保存null值对应的原值oldValue,然后覆盖原值,并返回oldValue
如果在table[0]Entry链表中没有找到就调用addEntry方法添加一个key为null的Entry

addEntry()方法

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

resize()方法

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 = newTable;  
       threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);  
   }  

createEntry()方法

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

添加到方法的具体操作,在添加之前先进行容量的判断,如果当前容量达到了阈值,并且需要存储到Entry[]数组中,先进性扩容操作,空充的容量为table长度的2倍。重新计算hash值,和数组存储的位置,扩容后的链表顺序与扩容前的链表顺序相反。然后将新添加的Entry实体存放到当前Entry[]位置链表的头部。在1.8之前,新插入的元素都是放在了链表的头部位置,但是这种操作在高并发的环境下容易导致死锁,所以1.8之后,新插入的元素都放在了链表的尾部。

五、get()/put()方法

详情点击下方
HashMap之get()方法详细解析
HashMap之put()方法详细解析

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值