Java知识梳理之HashMap的具体存储机制

HashMap 和 HashSet 是 Java Collection Framework 的两个重要成员,其中 HashMap 是 Map 接口的常用实现类,HashSet 是 Set 接口的常用实现类。虽然 HashMap 和 HashSet 实现的接口规范不同,但它们底层的 Hash 存储机制完全一样,甚至HashSet 本身就采用 HashMap 来实现的。

 

HashMap存储机制:

 

  1. 当创建一个 HashMap 时,系统会自动创建一个 table 数组来保存 HashMap 中的 Entry,每个数组都有一个固定的长度,这个数组的长度就是 HashMap 的容量。HashMap 包含如下几个构造器:

* HashMap():构建一个初始容量为 16,负载因子为 0.75 的 HashMap。

* HashMap(int initialCapacity):构建一个初始容量为 initialCapacity,负载因子为 0.75 的 HashMap。

* HashMap(int initialCapacity, float loadFactor):以指定初始容量、指定的负载因子创建一个 HashMap。

在创建过程中涉及到了两个参数:

  • initialCapacity 初始容量,是哈希表中桶(bucket)的数量,初始容量只是哈希表在创建时的容量。
  • loadFactor加载因子:是哈希表在其容量自动增加之前可以达到多满的一种尺度。默认是0.75,一般不用修改。增大加载因子可以减小Hash表的内存空间,但是会降低Hash表的查询效率,影响get()和put()等方法的效率。增大加载因子会增大Hash表的内存空间,但是会使查询效率更高。

2. HashMap具体存储实现:当我们调用put()方法添加Key-Value键值对到Map中时

public V put(K key, V value)  

{  

 // 如果 key 为 null,调用 putForNullKey 方法进行处理 

 if (key == null)  

     return putForNullKey(value);  

 // 根据 key 的 keyCode 计算 Hash 值 

 int hash = hash(key.hashCode());  

 // 搜索指定 hash 值在对应 table 中的索引 

     int i = indexFor(hash, table.length); 

 // 如果 i 索引处的 Entry 不为 null,通过循环不断遍历 e 元素的下一个元素 

 for (Entry<K,V> e = table[i]; e != null; e = e.next)  

 {  

     Object k;  

     // 找到指定 key 与需要放入的 key 相等(hash 值相同 

     // 通过 equals 比较放回 true) 

     if (e.hash == hash && ((k = e.key) == key  

         || key.equals(k)))  

     {  

         V oldValue = e.value;  

         e.value = value;  

         e.recordAccess(this);  

         return oldValue;  

     }  

 }  

 // 如果 i 索引处的 Entry 为 null,表明此处还没有 Entry  

 modCount++;  

 // 将 key、value 添加到 i 索引处 

 addEntry(hash, key, value, i);  

 return null;  

}  

具体将键值对存到哪个位置,完全是由Key决定的,跟Value没关。

3. 根据上面 put 方法的源代码可以看出,当程序试图将一个 key-value 对放入 HashMap 中时,程序首先根据该 key 的 hashCode() 返回值决定该 Entry 的存储位置:如果两个 Entry 的 key 的 hashCode() 返回值相同,那它们的存储位置相同。如果这两个 Entry 的 key 通过 equals 比较返回 true,新添加 Entry 的 value 将覆盖集合中原有 Entry 的 value,但 key 不会覆盖。如果这两个 Entry 的 key 通过 equals 比较返回 false,新添加的 Entry 将与集合中原有 Entry 形成 Entry 链,而且新添加的 Entry 位于 Entry 链的头部——具体说明继续看 addEntry() 方法的说明。

当向 HashMap 中添加 key-value 对,由其 key 的 hashCode() 返回值决定该 key-value 对(就是 Entry 对象)的存储位置。当两个 Entry 对象的 key 的 hashCode() 返回值相同时,将由 key 通过 eqauls() 比较值决定是采用覆盖行为(返回 true),还是产生 Entry 链(返回 false)。

void addEntry(int hash, K key, V value, int bucketIndex)  

{  

    // 获取指定 bucketIndex 索引处的 Entry  

    Entry<K,V> e = table[bucketIndex];     // ① 

    // 将新创建的 Entry 放入 bucketIndex 索引处,并让新的 Entry 指向原来的 Entry  

    table[bucketIndex] = new Entry<K,V>(hash, key, value, e);  

    // 如果 Map 中的 key-value 对的数量超过了极限 

    if (size++ >= threshold)  

        // 把 table 对象的长度扩充到 2 倍。 

        resize(2 * table.length);    // ② 

}  

如果bucketIndex的位置上有Entry对象,那么新对象和旧的Entry对象形成Entry链,如果NULL那么把新对象放到该bucketIndex上。无论何时,HashMap 的每个“桶”只存储一个元素(也就是一个 Entry),由于 Entry 对象可以包含一个引用变量(就是 Entry 构造器的的最后一个参数)用于指向下一个 Entry,因此可能出现的情况是:HashMap 的 bucket 中只有一个 Entry,但这个 Entry 指向另一个 Entry ——这就形成了一个 Entry 链。

4. HashMap读取机制:根据Key获取indexFor(hash(key.hashCode()), table.length);  当对应的bucket里面只有一个Entry时,直接取出。如果有一个Entry链,顺序遍历,直到找到想搜索的 Entry 为止。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值