热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Java并发编程HashMap(四)

HashMap,开发中最常用的数据结构之一,由数组加链表组成,以key--value键值对形式存在,HashMap的结构如

HashMap,开发中最常用的数据结构之一,由数组加链表组成,以key-->value键值对形式存在,HashMap的结构如下:

 HashMap类中有几个关键变量

/**默认的HashMap容器初始化大小16(1右移4位) ,必须是2的幂次方**/
DEFAULT_INITIAL_CAPACITY
&#61; 1 <<4/**最大的容器大小&#xff08;2的30次方&#xff09;**/
MAXIMUM_CAPACITY
&#61; 1 <<30/**默认的加载因子&#xff0c;当HashMap的容量大小超过初始化容量的75%后&#xff0c;会自动进行扩容操作**/
DEFAULT_LOAD_FACTOR
&#61; 0.75f/**阈值&#xff0c;当节点数超过8个会自动转化为红黑树**/
TREEIFY_THRESHOLD
&#61; 8/**阈值&#xff0c;当节点数低于6个时会转化为链表**/
UNTREEIFY_THRESHOLD
&#61; 6/**容器树化的最小表容量大小&#xff0c;如果没有达到该值&#xff0c;容器中有太多节点执行resize操作&#xff0c;MIN_TREEIFY_CAPACITY至少应该是TREEIFY_THRESHOLD的4倍&#xff0c;
以防止resizing与树化阈值之间的冲突*
*/
MIN_TREEIFY_CAPACITY
&#61; 64

 HashMap中有两个重要的内部类&#xff0c;Node、TreeNode

Node 代表HashMap中的普通节点&#xff08;未转化为红黑树之前&#xff0c;在jdk1.7中使用的是Entry表示&#xff09;&#xff0c;包含key&#xff0c;value, hash&#xff0c;next四个属性&#xff0c;Node类的结构如下&#xff1a;

 

TreeNode 代表Hahsmap中转化为红黑树后的节点&#xff08;jdk1.7中不存在该类&#xff09;&#xff0c;包含left&#xff08;左节点&#xff09;、right&#xff08;右节点&#xff09;、parent&#xff08;父亲节点&#xff09;、prev&#xff08;&#xff1f;&#xff1f;&#xff1f;&#xff09;、red&#xff08;树颜色的标识&#xff0c;非黑即红&#xff09;

HashMap的构造函数

hashMap对应的构造函数有四个&#xff0c;使用不同的构造函数&#xff0c;在后续的put操作中会有一定的区别。

/**前三个构造函数都不会去初始化hashMap操作&#xff08;生成node节点&#xff09;&#xff0c;在后续的put操作中会去则执行resize操作生成容量为16的hashMap对象&#xff0c;
第四个构造函数会根据传入的map对象大小生成一个hashMap对象*
*//**最简单的构造函数&#xff0c;没有任何参数&#xff0c;只会设置默认的阈值因子**/
public HashMap() {this.loadFactor &#61; DEFAULT_LOAD_FACTOR; // all other fields defaulted
}/**自定义初始化容量大小&#xff0c;默认的阈值因子是系统默认的0.75**/
public HashMap(int initialCapacity) {this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
/**自定义初始化容量大小以及阈值因子参数&#xff0c;构造函数中会进行判断**/
public HashMap(int initialCapacity, float loadFactor) {if (initialCapacity <0)throw new IllegalArgumentException("Illegal initial capacity: " &#43; initialCapacity);if (initialCapacity > MAXIMUM_CAPACITY)initialCapacity &#61; MAXIMUM_CAPACITY;if (loadFactor <&#61; 0 || Float.isNaN(loadFactor))throw new IllegalArgumentException("Illegal load factor: " &#43; loadFactor);this.loadFactor &#61; loadFactor;this.threshold &#61; tableSizeFor(initialCapacity);
}
/**传递Map参数来构造hashMap, **/
public HashMap(Mapextends K, ? extends V> m) {this.loadFactor &#61; DEFAULT_LOAD_FACTOR;putMapEntries(m, false);
}

 

HashMap的put操作

 hashMap的put操作核心方法是putVal方法

final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {Node[] tab; Node p; int n, i;/**如果table为空&#xff0c;初始化table对象, 默认生成大小为16&#xff0c;扩容因子为0.75的hashMap对象**/if ((tab &#61; table) &#61;&#61; null || (n &#61; tab.length) &#61;&#61; 0)n &#61; (tab &#61; resize()).length;/**根据key的hashCode计算在table中存放key/value键值对的位置&#xff08;前提是该位置没有存放其他键值对&#xff09;**/if ((p &#61; tab[i &#61; (n - 1) & hash]) &#61;&#61; null)tab[i] &#61; newNode(hash, key, value, null);/**计算位置已存在键值对时执行**/else {Node e; K k;/**hash值以及key值是否相同**/if (p.hash &#61;&#61; hash &&((k &#61; p.key) &#61;&#61; key || (key !&#61; null && key.equals(k))))e &#61; p;/**是否是红黑树节点**/else if (p instanceof TreeNode)e &#61; ((TreeNode)p).putTreeVal(this, tab, hash, key, value);else {/**链表形式将node节点插入到链表的后面&#xff08;jdk1.7中是将Entry插入到链表的表头&#xff09;**/for (int binCount &#61; 0; ; &#43;&#43;binCount) {if ((e &#61; p.next) &#61;&#61; null) {p.next &#61; newNode(hash, key, value, null);/**判断是否超过8个&#xff0c;超过会转化为红黑树**/if (binCount >&#61; TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);break;}if (e.hash &#61;&#61; hash &&((k &#61; e.key) &#61;&#61; key || (key !&#61; null && key.equals(k))))break;p &#61; e;}}/**如果存放新键值对的位置已存在值&#xff0c;且key值与新键值对相同&#xff0c;替换value值**/if (e !&#61; null) { // existing mapping for keyV oldValue &#61; e.value;if (!onlyIfAbsent || oldValue &#61;&#61; null)e.value &#61; value;afterNodeAccess(e);return oldValue;}}&#43;&#43;modCount;/**判断是否需要扩容**/if (&#43;&#43;size > threshold)resize();afterNodeInsertion(evict);return null;
}

resize操作

resize方法是用来初始化以及扩充2倍容量的方法

final Node[] resize() {Node[] oldTab &#61; table;int oldCap &#61; (oldTab &#61;&#61; null) ? 0 : oldTab.length;int oldThr &#61; threshold;int newCap, newThr &#61; 0;/**进行扩容操作**/if (oldCap > 0) {/**如果hashMap容量大小已超过最大值&#xff08;2^30&#xff09;&#xff0c;不在进行扩容&#xff0c;阈值大小设置为&#xff08;2^31-1&#xff09;&#xff0c;后续都不会在扩容**/if (oldCap >&#61; MAXIMUM_CAPACITY) {threshold &#61; Integer.MAX_VALUE;return oldTab;}/**扩容变成原容量的2倍&#xff0c;阈值也变成2倍**/else if ((newCap &#61; oldCap <<1) oldCap >&#61; DEFAULT_INITIAL_CAPACITY)newThr &#61; oldThr <<1; // double threshold
}/**对应HashMap(int initialCapacity)第一次put操作初始化的时候调用**/else if (oldThr > 0) // initial capacity was placed in thresholdnewCap &#61; oldThr;/**对应HashMap()第一次put操作初始化的时候调用**/else { // zero initial threshold signifies using defaultsnewCap &#61; DEFAULT_INITIAL_CAPACITY;newThr &#61; (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);}/**设置阈值**/if (newThr &#61;&#61; 0) {float ft &#61; (float)newCap * loadFactor;newThr &#61; (newCap float)MAXIMUM_CAPACITY ?(int)ft : Integer.MAX_VALUE);}threshold &#61; newThr;&#64;SuppressWarnings({"rawtypes","unchecked"})/**对于没有初始化的初始化操作&#xff0c;在调用构造函数是都不会初始化&#xff0c;只有在put时才会初始化&#xff08;以map为参数的构造函数除外&#xff0c;其实也是执行put时初始化&#xff09;**/Node[] newTab &#61; (Node[])new Node[newCap];table &#61; newTab;if (oldTab !&#61; null) {for (int j &#61; 0; j j) {Node e;if ((e &#61; oldTab[j]) !&#61; null) {oldTab[j] &#61; null;/**如果没有链表&#xff0c;直接赋值**/if (e.next &#61;&#61; null)newTab[e.hash & (newCap - 1)] &#61; e;else if (e instanceof TreeNode)((TreeNode)e).split(this, newTab, j, oldCap);else { // preserve order/**数据迁移部分对比&#xff1a;* 1.jdk1.8中将链表拆分为两个部分&#xff0c;一个是保存在原位置不变&#xff0c;一个是扩充前位置&#43;oldCap的位置&#xff0c;扩容数据迁移后链表的顺序没有改变&#xff1b;(1.8的效率更高&#xff09;* 2.jdk1.7中是通过双重循环遍历&#xff0c;使用node.hash & &#xff08;newCapacity - 1&#xff09;计算扩容后节点的位置&#xff0c;新节点的加入是存放到链表头&#xff0c;数据迁移后链表的顺序相反;**//**扩容后依然在扩容前位置的链表节点**/Node loHead &#61; null, loTail &#61; null;/**扩容后在扩容前位置&#43;oldCap位置的链表节点**/Node hiHead &#61; null, hiTail &#61; null;Node next;do {next &#61; e.next;if ((e.hash & oldCap) &#61;&#61; 0) {if (loTail &#61;&#61; null)loHead &#61; e;elseloTail.next &#61; e;loTail &#61; e;}else {if (hiTail &#61;&#61; null)hiHead &#61; e;elsehiTail.next &#61; e;hiTail &#61; e;}} while ((e &#61; next) !&#61; null);/**向扩容后的map中存放链表**/if (loTail !&#61; null) {loTail.next &#61; null;newTab[j] &#61; loHead;}if (hiTail !&#61; null) {hiTail.next &#61; null;newTab[j &#43; oldCap] &#61; hiHead;}}}}}return newTab;
}

 

get操作

get操作获取hashMap中数据实际调用的是getNode&#xff08;int hash,  Object key&#xff09;方法

final Node getNode(int hash, Object key) {Node[] tab; Node first, e; int n; K k;/**判断table是否为空&#xff0c;根据传递的key值获取的tab是否为空&#xff0c;此时获取的是链表的链表头结点**/if ((tab &#61; table) !&#61; null && (n &#61; tab.length) > 0 &&(first &#61; tab[(n - 1) & hash]) !&#61; null) {/**判断表头节点是否是获取的对象**/if (first.hash &#61;&#61; hash && // always check first node((k &#61; first.key) &#61;&#61; key || (key !&#61; null && key.equals(k))))return first;/**遍历链表获取节点**/if ((e &#61; first.next) !&#61; null) {if (first instanceof TreeNode)return ((TreeNode)first).getTreeNode(hash, key);do {if (e.hash &#61;&#61; hash &&((k &#61; e.key) &#61;&#61; key || (key !&#61; null && key.equals(k))))return e;} while ((e &#61; e.next) !&#61; null);}}return null;
}

remove操作

执行remove(Object key)以及remove(Object key, Object value)方法都是执行的removeNode方法

final Node removeNode(int hash, Object key, Object value, boolean matchValue, boolean movable) {Node[] tab; Node p; int n, index;/**判断table是否为空以及删除的key在table中是否存在**/if ((tab &#61; table) !&#61; null && (n &#61; tab.length) > 0 &&(p &#61; tab[index &#61; (n - 1) & hash]) !&#61; null) {Node node &#61; null, e; K k; V v;/**删除节点是链表头节点**/if (p.hash &#61;&#61; hash &&((k &#61; p.key) &#61;&#61; key || (key !&#61; null && key.equals(k))))node &#61; p;else if ((e &#61; p.next) !&#61; null) {if (p instanceof TreeNode)node &#61; ((TreeNode)p).getTreeNode(hash, key);else {/**删除节点非链表头的节点**/do {if (e.hash &#61;&#61; hash &&((k &#61; e.key) &#61;&#61; key ||(key !&#61; null && key.equals(k)))) {node &#61; e;break;}p &#61; e;} while ((e &#61; e.next) !&#61; null);}}/**执行删除操作&#xff0c;区分是否在表头**/if (node !&#61; null && (!matchValue || (v &#61; node.value) &#61;&#61; value ||(value !&#61; null && value.equals(v)))) {if (node instanceof TreeNode)((TreeNode)node).removeTreeNode(this, tab, movable);/**链表头结点的删除**/else if (node &#61;&#61; p)tab[index] &#61; node.next;/**非链表头结点的删除**/elsep.next &#61; node.next;&#43;&#43;modCount;--size;afterNodeRemoval(node);return node;}}return null;
}

以上的操作都是关于数组加链表的形式说明的&#xff0c;jdk1.8后当链表的节点个数达到8个以上时&#xff0c;会将链表转化为红黑树保存&#xff0c;当链表节点低于6个时&#xff0c;会再次退化为链表。

转:https://www.cnblogs.com/kaneziki/p/9672738.html



推荐阅读
  • HashMap的相关问题及其底层数据结构和操作流程
    本文介绍了关于HashMap的相关问题,包括其底层数据结构、JDK1.7和JDK1.8的差异、红黑树的使用、扩容和树化的条件、退化为链表的情况、索引的计算方法、hashcode和hash()方法的作用、数组容量的选择、Put方法的流程以及并发问题下的操作。文章还提到了扩容死链和数据错乱的问题,并探讨了key的设计要求。对于对Java面试中的HashMap问题感兴趣的读者,本文将为您提供一些有用的技术和经验。 ... [详细]
  • 本文详细介绍了Java中vector的使用方法和相关知识,包括vector类的功能、构造方法和使用注意事项。通过使用vector类,可以方便地实现动态数组的功能,并且可以随意插入不同类型的对象,进行查找、插入和删除操作。这篇文章对于需要频繁进行查找、插入和删除操作的情况下,使用vector类是一个很好的选择。 ... [详细]
  • ALTERTABLE通过更改、添加、除去列和约束,或者通过启用或禁用约束和触发器来更改表的定义。语法ALTERTABLEtable{[ALTERCOLUMNcolu ... [详细]
  • JDK源码学习之HashTable(附带面试题)的学习笔记
    本文介绍了JDK源码学习之HashTable(附带面试题)的学习笔记,包括HashTable的定义、数据类型、与HashMap的关系和区别。文章提供了干货,并附带了其他相关主题的学习笔记。 ... [详细]
  • 欢乐的票圈重构之旅——RecyclerView的头尾布局增加
    项目重构的Git地址:https:github.comrazerdpFriendCircletreemain-dev项目同步更新的文集:http:www.jianshu.comno ... [详细]
  • 合并列值-合并为一列问题需求:createtabletab(Aint,Bint,Cint)inserttabselect1,2,3unionallsel ... [详细]
  • 本文整理了Java面试中常见的问题及相关概念的解析,包括HashMap中为什么重写equals还要重写hashcode、map的分类和常见情况、final关键字的用法、Synchronized和lock的区别、volatile的介绍、Syncronized锁的作用、构造函数和构造函数重载的概念、方法覆盖和方法重载的区别、反射获取和设置对象私有字段的值的方法、通过反射创建对象的方式以及内部类的详解。 ... [详细]
  • 使用在线工具jsonschema2pojo根据json生成java对象
    本文介绍了使用在线工具jsonschema2pojo根据json生成java对象的方法。通过该工具,用户只需将json字符串复制到输入框中,即可自动将其转换成java对象。该工具还能解析列表式的json数据,并将嵌套在内层的对象也解析出来。本文以请求github的api为例,展示了使用该工具的步骤和效果。 ... [详细]
  • sklearn数据集库中的常用数据集类型介绍
    本文介绍了sklearn数据集库中常用的数据集类型,包括玩具数据集和样本生成器。其中详细介绍了波士顿房价数据集,包含了波士顿506处房屋的13种不同特征以及房屋价格,适用于回归任务。 ... [详细]
  • 本文介绍了游标的使用方法,并以一个水果供应商数据库为例进行了说明。首先创建了一个名为fruits的表,包含了水果的id、供应商id、名称和价格等字段。然后使用游标查询了水果的名称和价格,并将结果输出。最后对游标进行了关闭操作。通过本文可以了解到游标在数据库操作中的应用。 ... [详细]
  • 006_Redis的List数据类型
    1.List类型是一个链表结构的集合,主要功能有push,pop,获取元素等。List类型是一个双端链表的结构,我们可以通过相关操作进行集合的头部或者尾部添加删除元素,List的设 ... [详细]
  • MyBatis多表查询与动态SQL使用
    本文介绍了MyBatis多表查询与动态SQL的使用方法,包括一对一查询和一对多查询。同时还介绍了动态SQL的使用,包括if标签、trim标签、where标签、set标签和foreach标签的用法。文章还提供了相关的配置信息和示例代码。 ... [详细]
  • Python SQLAlchemy库的使用方法详解
    本文详细介绍了Python中使用SQLAlchemy库的方法。首先对SQLAlchemy进行了简介,包括其定义、适用的数据库类型等。然后讨论了SQLAlchemy提供的两种主要使用模式,即SQL表达式语言和ORM。针对不同的需求,给出了选择哪种模式的建议。最后,介绍了连接数据库的方法,包括创建SQLAlchemy引擎和执行SQL语句的接口。 ... [详细]
  • Whatsthedifferencebetweento_aandto_ary?to_a和to_ary有什么区别? ... [详细]
  • 重入锁(ReentrantLock)学习及实现原理
    本文介绍了重入锁(ReentrantLock)的学习及实现原理。在学习synchronized的基础上,重入锁提供了更多的灵活性和功能。文章详细介绍了重入锁的特性、使用方法和实现原理,并提供了类图和测试代码供读者参考。重入锁支持重入和公平与非公平两种实现方式,通过对比和分析,读者可以更好地理解和应用重入锁。 ... [详细]
author-avatar
Eosven_119
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有