HashMap源码解析
创始人
2025-01-15 20:36:02
0

目录

一: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[] tab; Node 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 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)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 e;     //hash(key),获取key的hash值     //调用getNode方法,见下面方法     return (e = getNode(hash(key), key)) == null ? null : e.value; }   final Node getNode(int hash, Object key) {     Node[] tab; Node 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)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[] resize() {         Node[] 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[] newTab = (Node[])new Node[newCap];     	//赋值     	table = newTab;     	//扩容操作,判断不为空证明不是初始化数组         if (oldTab != null) {             //遍历数组             for (int j = 0; j < oldCap; ++j) {                 Node 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)e).split(this, newTab, j, oldCap);                     //有下个节点的情况,并且没有树化(链表形式)                     else {                         //比如老数组容量是16,那下标就为0-15                         //扩容操作*2,容量就变为32,下标为0-31                         //低位:0-15,高位16-31                         //定义了四个变量                         //        低位头          低位尾                         Node loHead = null, loTail = null;                         //        高位头		   高位尾                         Node hiHead = null, hiTail = null;                         //下个节点                         Node 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可以在需要存储和检索数据的场景中发挥作用,并且由于其高效的存取方式,在大多数情况下,都是一个不错的选择。

相关内容

热门资讯

两分钟了解!亲朋游戏有辅助器的... 相信很多朋友都在电脑上玩过亲朋游戏有辅助器的吧,但是很多朋友都在抱怨用电脑玩起来不方便。为此小编给大...
2个攻略德扑之星是机制,wpk... 2个攻略德扑之星是机制,wpk安卓版外挂辅助器神器(Wepoke)透视辅助(2022已更新)(哔哩哔...
我来教大家!Wepoke安卓版... 我来教大家!Wepoke安卓版原来是有挂的((wPk))太夸张了其实都是有挂(2020已更新)(哔哩...
一起来讨论!牌乐门手机麻将可以... 一起来讨论!牌乐门手机麻将可以设置输赢的(辅助挂)其实一直总是有挂(2025已更新)(哔哩哔哩),牌...
关于!WPK工具原来是有挂的(... 您好,wPk这款游戏可以开挂的,确实是有挂的,需要了解加微【841106723】很多玩家在这款游戏中...
十分钟了解!星悦福建麻将到底有... 十分钟了解!星悦福建麻将到底有挂(辅助挂)其实一直都是有挂(2020已更新)(哔哩哔哩)是一款可以让...
七种猫腻!wpk辅助器(WPk... 七种猫腻!wpk辅助器(WPk)软件透明挂(辅助挂)外挂辅助器软件(2023已更新)(哔哩哔哩)是一...
两个小技巧微扑克wpk安全的,... 两个小技巧微扑克wpk安全的,wpk胜率外挂透明挂助手(wePoke)软件透明挂(2025已更新)(...
最新技巧!方片十三张可以装软件... 最新技巧!方片十三张可以装软件的(辅助挂)其实是有挂的(2021已更新)(哔哩哔哩);方片十三张可以...
线上教程!德州Wepoke原来... 线上教程!德州Wepoke原来都是有挂((WepOke))太离谱了其实都是有挂(2024已更新)(哔...