HashMap源码分析(保姆式注解):三大方法(构造、Put、Remove) ;附带面试考点及博主免费答疑
前言
想速成的建议直接背考点(第四点看)。建议知其所以然~~学习知识要慢慢来。
一、构造方法HashMap()
1.时间复杂度所以导致了有这三个数据结构(数组+链表+红黑树:接近二分查找)构成HashMap
2.链表转成红黑树:链表长度大于8,8是泊松分布决定的(跟原始数组长度设置为16的原因一样)
3.扩容一般都是快满的时候扩容,数组已经填了0.75倍了。
4.没有声明任何空间,在构造HashMap的时候(4种构造方法),进行调用方法的时候,会先判定table数组大小,如果为空会分配10
//空参构造
//有参构造,传入Int,初始化容量,进入后会判定大小,不能超过最大容量MAXIMUM_CAPACITY=1<30;
给threshold赋值(传入的容量) threshold: n. 入口;门槛;开始;极限;临界值
比如传入为12,
n=12-1=11
//0000 1011=11
//0000 0101=5
//0000 1111=15=n,继续进行下一行左移两位,最后会求出,你给的容量,返回2的n次方
//传入map集合的构造方法
final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
//传入集合的大小int s = m.size();if (s > 0) {
//判断Node<K,V>[] table数组是否为nullif (table == null) { // pre-sizefloat ft = ((float)s / loadFactor) + 1.0F;int t = ((ft < (float)MAXIMUM_CAPACITY) ?(int)ft : MAXIMUM_CAPACITY);if (t > threshold)threshold = tableSizeFor(t);}else if (s > threshold)
//扩容resize();for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {K key = e.getKey();V value = e.getValue();putVal(hash(key), key, value, false, evict);}}}
//获得key的hash值
static final int hash(Object key) {int h;
//int 32位
//对象的hashcode值^(异或) 对象的hashcode值的高位(前16位)
//目的:提高hashcode的随机性return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}例如:1101 1101=hascode ,数组容量15=0000 1111,求余直接&即可得到余数,原来的默认是1101,操作后成为0100,增加随机性
如果只用低位的话,可能高位不一样,低位一样会造成很多重复key,所以加上高位的话,那么存的更均匀随机。
9%15=9
0001 1001
0011 10011101 1101
0000 1101
----------^异或
1101 0100
0000 1111
---------&
0000 0100
//给初始值和负载因子的构造方法
二、HashMap的Put方法详解
由于注释写的太仔细了。所以就没有一个一个单独分析。如果有什么不懂的。可以到页面最后加群问群众。免费答疑!
public V put(K key, V value) {
//根据传入参数key,获取hashcode值return putVal(hash(key), key, value, false, true);
}
开始神奇的魔幻之旅了。
//onlyIfAbsent=false
//evict=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;
//判断table数组是否为空或长度为0if ((tab = table) == null || (n = tab.length) == 0)
//初始化tablen = (tab = resize()).length;
//比如当前数组是n=16-1=15&hash值=i
//i=元素在tab数组中存储的位置
//p=tab[i],p链表==null
//(n-1)&hash取余运算 目的 优化计算速度。&比%符号计算速度更快if ((p = tab[i = (n - 1) & hash]) == null)
//创建节点,直接存放tab[i]位置tab[i] = newNode(hash, key, value, null);else {Node<K,V> e; K k;if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))e = p;
//判断是否是树结构 JDK1.8=红黑树优化方案else if (p instanceof TreeNode)
//基于红黑树的插入逻辑e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);else {
//链表插入元素for (int binCount = 0; ; ++binCount) {
//判断p的下一个元素是否nullif ((e = p.next) == null) {
//p的下一个元素=newNode(hash, key, value, null);p.next = newNode(hash, key, value, null);
//判断当前链表的数量是否大于数结构的阈值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;}}
//判断插入的值是否存在hashmap中if (e != null) { // existing mapping for keyV oldValue = e.value;if (!onlyIfAbsent || oldValue == null)e.value = value;afterNodeAccess(e);return oldValue;}}
//修改次数+1++modCount;
//判断当前数组大小是否大于阈值if (++size > threshold)
//扩容resize();afterNodeInsertion(evict);return null;
}
Put方法里面主要是有一个扩容,这个是面试必问(如果问了HashMap)
final Node<K,V>[] resize() {
//数组初始值Node<K,V>[] oldTab = table;
//扩容前的初始化int oldCap = (oldTab == null) ? 0 : oldTab.length;int oldThr = threshold;//加了负载因子的容量:阈值容量
//扩容后的初始化int newCap, newThr = 0;if (oldCap > 0) {if (oldCap >= MAXIMUM_CAPACITY) {threshold = Integer.MAX_VALUE;return oldTab;}
//newCap = oldCap << 1 乘以2=新的容量else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&oldCap >= DEFAULT_INITIAL_CAPACITY)
//oldThr 乘以2=newThrnewThr = oldThr << 1; // double threshold}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);}if (newThr == 0) {float ft = (float)newCap * loadFactor;newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?(int)ft : Integer.MAX_VALUE);
//到了两倍容量发现超过最大容量就直接给最大容量}threshold = newThr;
//创建一个新的*2的容量的数组@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个位置的元素if ((e = oldTab[j]) != null) {
//清空原数组,真秒,赋值的同时还进行了比较oldTab[j] = null;
//判断原有j位置上是否有值if (e.next == null)
//重新计算位置,进行元素保存
//例如 16
//[e.hash*31=新元素]newTab[e.hash & (newCap - 1)] = e;else if (e instanceof TreeNode)
//对树开始拆分((TreeNode<K,V>)e).split(this, newTab, j, oldCap);else { // preserve orderNode<K,V> loHead = null, loTail = null;Node<K,V> hiHead = null, hiTail = null;Node<K,V> next;do {
//遍历链表,将链表节点按照顺序进行分组next = e.next;
//计算原有元素在扩容后,还在原位置if ((e.hash & oldCap) == 0) {
//old链表添加到一组if (loTail == null)loHead = e;elseloTail.next = e;loTail = e;}
//计算原有元素在扩容后,不在原位置else {
//new链表添加到一组if (hiTail == null)hiHead = e;elsehiTail.next = e;hiTail = e;}} while ((e = next) != null);if (loTail != null) {loTail.next = null;newTab[j] = loHead;}if (hiTail != null) {hiTail.next = null;
//原位置j+原容量=新的位置newTab[j + oldCap] = hiHead;}}}}}
//扩容后的数组return newTab;
}
三、Hashmap的删除remove方法
public V remove(Object key) {Node<K,V> e;return (e = removeNode(hash(key), key, null, false, true)) == null ?null : e.value;
}
final Node<K,V> removeNode(int hash, Object key, Object value,boolean matchValue, boolean movable) {Node<K,V>[] tab; Node<K,V> p; int n, index;if ((tab = table) != null && (n = tab.length) > 0 &&
//元素要存储的位置p(p = tab[index = (n - 1) & hash]) != null) {Node<K,V> node = null, e; K k; V v;
//hash没有冲突情况if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))
//定位删除的节点Nodenode = p;
//有冲突,不只是一个元素在同一个位置else if ((e = p.next) != null) {if (p instanceof TreeNode)
//红黑树,定位删除元素node = ((TreeNode<K,V>)p).getTreeNode(hash, key);else {
//链表,定位删除元素do {if (e.hash == hash &&((k = e.key) == key ||(key != null && key.equals(k)))) {node = e;break;}p = e;} while ((e = e.next) != null);}}
//node要删除的元素if (node != null && (!matchValue || (v = node.value) == value ||(value != null && value.equals(v)))) {if (node instanceof TreeNode)
//红黑树删除节点((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);else if (node == p)
//链表删除节点,只有一个节点tab[index] = node.next;else
//数组中p位置的对象下一个元素=删除元素的下一个元素p.next = node.next;++modCount;--size;afterNodeRemoval(node);return node;}}return null;
}
四、面试考点
知识点1:
树化 链表到了8个
退化 树的到了6个节点
知识点2:
table数组是2的次方两个原因:1.好计算位置计算余数,计算新的hash值 2.方便数据迁移,16变32,直接看倒数第五位是否为1,为1就加数组,为0就不变。
知识点3:
一个int 4位,32位计算,但是余数是直接只能看后4位,因为默认容器是16(概率统计)。将取模弄成位运算。
知识点4:
整数的hash值就是本身
知识点5(sizectl):
ConcurrentHashMap--putval-initTbale-sizeCtl(-1为初始化,-N为扩容,整数代表当前数组要扩容时的阈值)
知识点6:
MAX_ARRAY_SIZE 为什么要减去8
因为Aarry 到class 中还有一些底层的信息和原信息。
知识点7:
HashMap的遍历
final Node<K,V> nextNode() {Node<K,V>[] t;Node<K,V> e = next;if (modCount != expectedModCount)
//报错的原因throw new ConcurrentModificationException();if (e == null)throw new NoSuchElementException();if ((next = (current = e).next) == null && (t = table) != null) {
//寻找数组中下一个hash槽不为空的节点do {} while (index < t.length && (next = t[index++]) == null);}return e;
}
在迭代hashmap的时候不能删除,因为其中有一个报错条件满足了,modCount != expectedModCount,只要有添加删除都会modCount+1,所以期望的不相等。
知识点8:
JDK1.7是头插法,JDK1.8是尾插法。对于链表的时候
可以边画边讲。
知识点9:
其中用到红黑树,我来谈谈树的升级
树->BST(二叉搜索树,如果是递增那么成单支结果)->AVL(平衡树)->红黑树
无序->二分查找变有序->为了防止全部插大的,没有分支,规定最长和最短只能相差1->但是插入的时候需要有序,要用到旋转,增大了插入的时间->为了平衡插入与搜索,规定最长是最短的两倍。
五、ConcurrentHashMap 传送点
https://blog.csdn.net/qq_40262372/article/details/112674917
六、高效刷题步骤及试题汇总:
https://blog.csdn.net/qq_40262372/article/details/112556249
七、如有疑问可加QQ群讨论:725936761 博主免费答疑
欢迎大家一起讨论进步。
HashMap源码分析(保姆式注解):三大方法(构造、Put、Remove) ;附带面试考点及博主免费答疑相关推荐
- HashMap源码分析(转载)
一.HashMap概述 HashMap基于哈希表的 Map 接口的实现.此实现提供所有可选的映射操作,并允许使用 null 值和 null 键.(除了不同步和允许使用 null 之外,HashMap ...
- 在参考了众多博客之后,我写出了多达三万字的HashMap源码分析,比我本科毕业论文都要精彩
HashMap源码分析 以下代码都是基于java8的版本 HashMap简介 源码: public class HashMap<K,V> extends AbstractMap<K, ...
- HashMap 源码分析与常见面试题
文章目录 HashMap 源码分析 jdk 1.7 内部常量 静态内部类 Holder 类 构造方法 put 过程 put 整体流程图 jdk 1.8 增加的常量 Node 类 Hash 值计算的变化 ...
- java基础之HashMap源码分析
目录 1. HashMap原理分析 1.1. HashMap继承体系 1.2.Node数据结构分析 1.3.底层储存结构 1.3.1.put方法分析 1.4.hash碰撞 1.4.1.key值的唯一性 ...
- 【Java源码分析】Java8的HashMap源码分析
Java8中的HashMap源码分析 源码分析 HashMap的定义 字段属性 构造函数 hash函数 comparableClassFor,compareComparables函数 tableSiz ...
- JDK 1.6 HashMap 源码分析
前言 前段时间研究了一下JDK 1.6 的 HashMap 源码,把部份重要的方法分析一下,当然HashMap中还有一些值得研究得就交给读者了,如有不正确之处还望留言指正. 准备 需要熟悉数组 ...
- Java类集框架 —— HashMap源码分析
HashMap是基于Map的键值对映射表,底层是通过数组.链表.红黑树(JDK1.8加入)来实现的. HashMap结构 HashMap中存储元素,是将key和value封装成了一个Node,先以一个 ...
- 查询已有链表的hashmap_源码分析系列1:HashMap源码分析(基于JDK1.8)
1.HashMap的底层实现图示 如上图所示: HashMap底层是由 数组+(链表)=(红黑树) 组成,每个存储在HashMap中的键值对都存放在一个Node节点之中,其中包含了Key-Value ...
- 源码分析系列1:HashMap源码分析(基于JDK1.8)
1.HashMap的底层实现图示 如上图所示: HashMap底层是由 数组+(链表)+(红黑树) 组成,每个存储在HashMap中的键值对都存放在一个Node节点之中,其中包含了Key-Value ...
- 从源码分析 Spring 基于注解的事务
从源码分析 Spring 基于注解的事务 在spring引入基于注解的事务(@Transactional)之前,我们一般都是如下这样进行拦截事务的配置: <!-- 拦截器方式配置事务 --> ...
最新文章
- sublime运行错误
- 当AI实现多任务学习,它究竟能做什么?
- Windows上安装Apache
- Visual C++ 中的重大更改
- hdu 1569 方格取数(2) 最大点权独立集
- for循环和数组练习
- TIOBE 9 月编程语言榜:Python 居然超越了 C++!
- composer 下载包慢的解决方法
- java java se_Java SE 9:不可变列表的工厂方法
- 20200728每日一句
- Qt基本控件及三大布局
- 华罗庚杯成绩查询2021高考成绩,第22届华杯赛入围决赛分数线已定,明天起可查询成绩...
- 2022五一劳动节虾皮仓库物流放假安排
- FrameMaker 格式的本地化流程
- js:ajax的get方法实现简单的搜索框提示
- 运维派 » 你有自己的Web缓存知识体系吗?
- Python+PyCharm的一些基本设置:安装使用、注册码、显示行号、字体大小和快捷键等常用设置...
- lerna import报错
- 推荐一个自动破解替换密码的工具
- 可视化工具VisIt安装使用教程(Windows)
热门文章
- eclipse的控制台显示有问题,关闭Limit console output
- 好看的流程审批html,审批流程(加班)驳回(流程被删除).html
- feign扫描_微服务实战SpringCloud之Feign简介及使用
- 计算机数据处理规模大小分为,计算机组成原理题集样稿.doc
- paypalsdk集成php,php核心paypal sdk
- 【2019银川网络赛D:】Take Your Seat(概率--递推+思维)
- 信用评分卡模型分析(基于Python)--理论部分
- MATLAB--二分法
- 环信 java接口实例_环信即时聊天与java后台接口对接demo
- 极客大学架构师训练营 JVM虚拟机原理 JVM垃圾回收原理 Java编程优化 第17课 听课总结