一、Java源码解读 -- HashMap 的底层实

本文深入探讨了JDK 1.7中HashMap的底层实现原理,详细分析了其put和get方法的工作流程,包括扩容机制和冲突解决策略。
相对来说大家对hashMap 这个集合还是比较熟悉的,在这里,我结合网上查阅的一些资料以及定位 jdk1.7底层源码来阐述一下HashMap的底层实现(这里是值jdk1.7版本的,1.8是在底层上重新做了修改的,加大查询效率,这里暂时不讨论)首先我们打开HashMap的源码:

  1. public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable{  ...  }
从继承结构上来说,HashMap 是 实现了 AbstractMap  继承 Map、Cloneable、Serializable
先不说其他的,熟悉Map体现的人都知道,HashMap 是继承 Map 的
首先我们来看一下Map的结构图:
打开Map接口,我们可以看到:
Map接口定义了方法有
size() 
isEmpty() , 
containsKey(Object key);
containsValue();
V get(Object key);
V put(K key, V value);
V remove(Object key);
putAll(Map<? extends K, ? extends V> m);


那么我们从 开始创建一个HashMap 的时候开始讲解一下HashMap是如何运作的,
1、 首先我们创建一个HashMap 对象(直接使用 HashMap 去声明,可以方便定位源代码)
  1. HashMap<String, String>  hmap = new HashMap<String, String>();
这里话主要是将HashMap内部的泛型类型确定为自己声明的类型,以及创建一些初始化数据,
如: Entry<String,String> table [] 


那么,当我们想这个HashMap 添加 key - value 的时候,hashmap再后面做了什么
  1. hmap.put("key1","value1");
这里我们进入hmap 的方法内部(源码是没有标记这个注释的,是为了后面):
  1.  public V put(K key, V value) {
  1.         if (table == EMPTY_TABLE) {             // 标记 1
  1.             inflateTable(threshold);
  1.         }
  1.         if (key == null)
  1.             return putForNullKey(value);
  1.         int hash = hash(key);
  1.         int i = indexFor(hash, table.length);
  1.         for (Entry<K,V> e = table[i]; e != null; e = e.next) {
  1.             Object k;
  1.             if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
  1.                 V oldValue = e.value;
  1.                 e.value = value;
  1.                 e.recordAccess(this);
  1.                 return oldValue;
  1.             }
  1.         }

  1.         modCount++;
  1.         addEntry(hash, key, value, i);
  1.         return null;
  1.     }
一、HashMap 的 put()方法解析
1、 判断table 是否为空,如果为空,初始化一个固定大小的 数组,(table是存储数据的地方)
2、 判断 key 是否为空,如果为空 则直接在 table[0] 的位置 添加数据
3、 计算 key 的 hash值(如何计算这里暂不阐述), 然后 根据  hash值 和  现table的长度进行计算去摸获取 key-value 在 table数组的位置 index ;
4、 获取table[ index ] 位置的 Entry<K,V>   e , 并且遍历 e (因为Entry <K,V> 内部有链表的结构,即 next 属性 是Enttry<K,V> ,表示 e 是一个链表结构),说到这里,就是在网上很多人说HashMap 是又 数组 和链表结构构成的,说的就是 数组存放hash去摸后不同的放的位置, 链表是指具 取摩 值相同时 ,存放在同一个链表下,并且是新值永远存放在链表的Header位置上,
4.1、 遍历 e :这里主要是为了将具有查询 key 值是否已经被存入其中,主要是通过 判断 hash值,和(equals  或者  ==  是否同一对象)
4.2、 如果判断是同一 key(hash值 和 equeal 相同) ,将旧值替换成新的值,并将旧值返回
5、如果在 4.1 的循环体 中没有遇到 原 key 和 操作的 key 相同,那么就走添加新的Entry<K,V> 实体的流程 ,hashMap 数量modCount +1;
6、进行添加新的 Entry 对象,addEntry<int hash, K key , Value , int bucketIndex >
6.1、addEntry() 方法的的实现 源码实现如下:
  1.     void addEntry(int hash, K key, V value, int bucketIndex) {
  1.         if ((size >= threshold) && (null != table[bucketIndex])) {
  1.             resize(2 * table.length);
  1.             hash = (null != key) ? hash(key) : 0;
  1.             bucketIndex = indexFor(hash, table.length);
  1.         }

  1.         createEntry(hash, key, value, bucketIndex);
  1.     }
(1)、首先 在真正添加之前做了一下map是否需要扩容的判断,即map的大小size是否超过扩容的门槛 threshold ,并且当前位置上已经key - value 的值存放,这时就需要执行 扩容方法:resize(2* table.length);这里我们可以看到 长度扩展为原长度的两倍,并重新计算扩容后的hash值然后在进行添加
(2)、resize ():大致做了将扩容前的数据数组 放到新的 安装规则 放入 新的数组内,并设置扩容门槛,(当然,如果原数组的程度达到最大长度是不执行扩容,直接跳出)
(3)、最后进行 hashmap 的添加工作,取出当前位置的数据(可能为空),然后使用Entry 的构成函数进行数据的生成,即,新的Entry放在首位,老的Entry 放在心的next位置,形成一个链表。
  1.     void createEntry(int hash, K key, V value, int bucketIndex) {
  1.         Entry<K,V> e = table[bucketIndex];
  1.         table[bucketIndex] = new Entry<>(hash, key, value, e);
  1.         size++;
  1.     }
讲到这里,put方法的添加过程就算是全部看完了

二、HashMap 的 get()方法
get方法的相对来谁就会简单点,下面是get方法的源代码:
  1.     public V get(Object key) {
  1.         if (key == null)
  1.             return getForNullKey();
  1.         Entry<K,V> entry = getEntry(key);
  1.         return null == entry ? null : entry.getValue();
  1.     }
1、如果key 值为空时,直接从第一个元素位置上获取值,遍历链表判断key 是否为 null 然后返回对应的结果Entry 对象;
2.不为空的时候,使用getEntry 返回对象的Entry对象; getEntry()方法源码如下
  1.    final Entry<K,V> getEntry(Object key) {
  1.         if (size == 0) {
  1.             return null;
  1.         }

  1.         int hash = (key == null) ? 0 : hash(key);
  1.         for (Entry<K,V> e = table[indexFor(hash, table.length)];
  1.              e != null;
  1.              e = e.next) {
  1.             Object k;
  1.             if (e.hash == hash &&
  1.                 ((k = e.key) == key || (key != null && key.equals(k))))
  1.                 return e;
  1.         }
  1.         return null;
  1.     }
 
2.1、首先是一些限制性条件,在map的大小 为 0  的时候,直接返回 null;
2.2、获取 key 的hash 值 (如果key 为 null 则hash值为 0)
2.3、 获取key 的 hash值 和table的长度取模  得到数据所在数组的位置,然后遍历 entry链表对象,从而比较  entry 对象的key 值,如果是匹配正确,则返回当前的 entry 对象
2.4、 如果没有匹配到 key  则返回null。


说到这里,HashMap 的两个主要的 put  和 get 方法就算讲完,在随后的机会里,再来讲讲在  jdk1.8  中HashMap 的变换,这里先简单说一下, 1.8中的HashMap  是将 以前版本数组的每个元素 的链表 改变成 红黑树结构  



如果文中出现错误,请大家及时指正




评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值