HashMap源码解析

avatar
作者
筋斗云
阅读量:1

目录

一:put方法流程

二:get方法

三:扩容机制


一:put方法流程

public V put(K key, V value) {     return putVal(hash(key), key, value, false, true); }  final V putVal(int hash, K key, V value, boolean onlyIfAbsent,                    boolean evict) {     Node<K,V>[] tab; Node<K,V> p; int n, i;     //判断数组是否未初始化     if ((tab = table) == null || (n = tab.length) == 0)         //如果未初始化,调用resize方法 进行初始化         n = (tab = resize()).length;     //通过 & 运算求出该数据(key)的数组下标并判断该下标位置是否有数据     if ((p = tab[i = (n - 1) & hash]) == null)         //如果没有,直接将数据放在该下标位置         tab[i] = newNode(hash, key, value, null);     //该数组下标有数据的情况     else {         Node<K,V> e; K k;         //判断该位置数据的key和新来的数据是否一样         if (p.hash == hash &&             ((k = p.key) == key || (key != null && key.equals(k))))             //如果一样,证明为修改操作,该节点的数据赋值给e,后边会用到             e = p;         //判断是不是红黑树         else if (p instanceof TreeNode)             //如果是红黑树的话,进行红黑树的操作             e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);         //新数据和当前数组既不相同,也不是红黑树节点,证明是链表         else {             //遍历链表             for (int binCount = 0; ; ++binCount) {                 //判断next节点,如果为空的话,证明遍历到链表尾部了                 if ((e = p.next) == null) {                     //把新值放入链表尾部                     p.next = newNode(hash, key, value, null);                     //因为新插入了一条数据,所以判断链表长度是不是大于等于8                     if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st                         //如果是,进行转换红黑树操作                         treeifyBin(tab, hash);                     break;                 }                 //判断链表当中有数据相同的值,如果一样,证明为修改操作                 if (e.hash == hash &&                     ((k = e.key) == key || (key != null && key.equals(k))))                     break;                 //把下一个节点赋值为当前节点                 p = e;             }         }         //判断e是否为空(e值为修改操作存放原数据的变量)         if (e != null) { // existing mapping for key             //不为空的话证明是修改操作,取出老值             V oldValue = e.value;             //一定会执行  onlyIfAbsent传进来的是false             if (!onlyIfAbsent || oldValue == null)                 //将新值赋值当前节点                 e.value = value;             afterNodeAccess(e);             //返回老值             return oldValue;         }     }     //计数器,计算当前节点的修改次数     ++modCount;     //当前数组中的数据数量如果大于扩容阈值     if (++size > threshold)         //进行扩容操作         resize();     //空方法     afterNodeInsertion(evict);     //添加操作时 返回空值     return null; }

二:get方法

public V get(Object key) {     Node<K,V> e;     //hash(key),获取key的hash值     //调用getNode方法,见下面方法     return (e = getNode(hash(key), key)) == null ? null : e.value; }   final Node<K,V> getNode(int hash, Object key) {     Node<K,V>[] tab; Node<K,V> first, e; int n; K k;     //找到key对应的桶下标,赋值给first节点     if ((tab = table) != null && (n = tab.length) > 0 &&         (first = tab[(n - 1) & hash]) != null) {         //判断hash值和key是否相等,如果是,则直接返回,桶中只有一个数据(大部分的情况)         if (first.hash == hash && // always check first node             ((k = first.key) == key || (key != null && key.equals(k))))             return first;                  if ((e = first.next) != null) {             //该节点是红黑树,则需要通过红黑树查找数据             if (first instanceof TreeNode)                 return ((TreeNode<K,V>)first).getTreeNode(hash, key);                          //链表的情况,则需要遍历链表查找数据             do {                 if (e.hash == hash &&                     ((k = e.key) == key || (key != null && key.equals(k))))                     return e;             } while ((e = e.next) != null);         }     }     return null; }

三:扩容机制

//扩容、初始化数组 final Node<K,V>[] resize() {         Node<K,V>[] oldTab = table;     	//如果当前数组为null的时候,把oldCap老数组容量设置为0         int oldCap = (oldTab == null) ? 0 : oldTab.length;         //老的扩容阈值     	int oldThr = threshold;         int newCap, newThr = 0;         //判断数组容量是否大于0,大于0说明数组已经初始化     	if (oldCap > 0) {             //判断当前数组长度是否大于最大数组长度             if (oldCap >= MAXIMUM_CAPACITY) {                 //如果是,将扩容阈值直接设置为int类型的最大数值并直接返回                 threshold = Integer.MAX_VALUE;                 return oldTab;             }             //如果在最大长度范围内,则需要扩容  OldCap << 1等价于oldCap*2             //运算过后判断是不是最大值并且oldCap需要大于16             else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&                      oldCap >= DEFAULT_INITIAL_CAPACITY)                 newThr = oldThr << 1; // double threshold  等价于oldThr*2         }     	//如果oldCap<0,但是已经初始化了,像把元素删除完之后的情况,那么它的临界值肯定还存在,       			如果是首次初始化,它的临界值则为0         else if (oldThr > 0) // initial capacity was placed in threshold             newCap = oldThr;         //数组未初始化的情况,将阈值和扩容因子都设置为默认值     	else {               // zero initial threshold signifies using defaults             newCap = DEFAULT_INITIAL_CAPACITY;             newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);         }     	//初始化容量小于16的时候,扩容阈值是没有赋值的         if (newThr == 0) {             //创建阈值             float ft = (float)newCap * loadFactor;             //判断新容量和新阈值是否大于最大容量             newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?                       (int)ft : Integer.MAX_VALUE);         }     	//计算出来的阈值赋值         threshold = newThr;         @SuppressWarnings({"rawtypes","unchecked"})         //根据上边计算得出的容量 创建新的数组            	Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];     	//赋值     	table = newTab;     	//扩容操作,判断不为空证明不是初始化数组         if (oldTab != null) {             //遍历数组             for (int j = 0; j < oldCap; ++j) {                 Node<K,V> e;                 //判断当前下标为j的数组如果不为空的话赋值个e,进行下一步操作                 if ((e = oldTab[j]) != null) {                     //将数组位置置空                     oldTab[j] = null;                     //判断是否有下个节点                     if (e.next == null)                         //如果没有,就重新计算在新数组中的下标并放进去                         newTab[e.hash & (newCap - 1)] = e;                    	//有下个节点的情况,并且判断是否已经树化                     else if (e instanceof TreeNode)                         //进行红黑树的操作                         ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);                     //有下个节点的情况,并且没有树化(链表形式)                     else {                         //比如老数组容量是16,那下标就为0-15                         //扩容操作*2,容量就变为32,下标为0-31                         //低位:0-15,高位16-31                         //定义了四个变量                         //        低位头          低位尾                         Node<K,V> loHead = null, loTail = null;                         //        高位头		   高位尾                         Node<K,V> hiHead = null, hiTail = null;                         //下个节点                         Node<K,V> next;                         //循环遍历                         do {                             //取出next节点                             next = e.next;                             //通过 与操作 计算得出结果为0                             if ((e.hash & oldCap) == 0) {                                 //如果低位尾为null,证明当前数组位置为空,没有任何数据                                 if (loTail == null)                                     //将e值放入低位头                                     loHead = e;                                 //低位尾不为null,证明已经有数据了                                 else                                     //将数据放入next节点                                     loTail.next = e;                                 //记录低位尾数据                                 loTail = e;                             }                             //通过 与操作 计算得出结果不为0                             else {                                  //如果高位尾为null,证明当前数组位置为空,没有任何数据                                 if (hiTail == null)                                     //将e值放入高位头                                     hiHead = e;                                 //高位尾不为null,证明已经有数据了                                 else                                     //将数据放入next节点                                     hiTail.next = e;                                //记录高位尾数据                                	hiTail = e;                             }                                                      }                          //如果e不为空,证明没有到链表尾部,继续执行循环                         while ((e = next) != null);                         //低位尾如果记录的有数据,是链表                         if (loTail != null) {                             //将下一个元素置空                             loTail.next = null;                             //将低位头放入新数组的原下标位置                             newTab[j] = loHead;                         }                         //高位尾如果记录的有数据,是链表                         if (hiTail != null) {                             //将下一个元素置空                             hiTail.next = null;                             //将高位头放入新数组的(原下标+原数组容量)位置                             newTab[j + oldCap] = hiHead;                         }                     }                 }             }         }     	//返回新的数组对象         return newTab;     }

四:总结

HashMap是Java中常用的数据结构之一,用于存储键值对的键值对。以下是HashMap的几个常见的使用场景总结:

1. 缓存管理:HashMap可以用于实现缓存功能,将数据存储在HashMap中,以键值对的形式保存。可以通过查询HashMap来获取需要的数据,避免了再次计算或查询数据库的开销。

2. 数据索引:HashMap是一种快速查找数据的数据结构,可以根据键快速找到对应的值。因此,HashMap可以用于构建索引结构,提高数据的检索效率。

3. 字典:HashMap可以用于实现字典功能,将单词与对应的意义作为键值对存储在HashMap中。通过查询键来获取对应的意义,实现快速查找。

4. 频率统计:HashMap可以用于统计数据中各个元素出现的频率。可以将元素作为键,出现的次数作为值,通过对值进行排序或查询,获取频率最高的元素。

5. 数据存储和检索:HashMap是一种高效的数据结构,可以用于存储和检索大量数据。可以根据键快速找到对应的值,提高数据的存取效率。

总之,HashMap可以在需要存储和检索数据的场景中发挥作用,并且由于其高效的存取方式,在大多数情况下,都是一个不错的选择。

广告一刻

为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!