前言

想速成的建议直接背考点(第四点看)。建议知其所以然~~学习知识要慢慢来。

一、构造方法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) ;附带面试考点及博主免费答疑相关推荐

  1. HashMap源码分析(转载)

    一.HashMap概述 HashMap基于哈希表的 Map 接口的实现.此实现提供所有可选的映射操作,并允许使用 null 值和 null 键.(除了不同步和允许使用 null 之外,HashMap  ...

  2. 在参考了众多博客之后,我写出了多达三万字的HashMap源码分析,比我本科毕业论文都要精彩

    HashMap源码分析 以下代码都是基于java8的版本 HashMap简介 源码: public class HashMap<K,V> extends AbstractMap<K, ...

  3. HashMap 源码分析与常见面试题

    文章目录 HashMap 源码分析 jdk 1.7 内部常量 静态内部类 Holder 类 构造方法 put 过程 put 整体流程图 jdk 1.8 增加的常量 Node 类 Hash 值计算的变化 ...

  4. java基础之HashMap源码分析

    目录 1. HashMap原理分析 1.1. HashMap继承体系 1.2.Node数据结构分析 1.3.底层储存结构 1.3.1.put方法分析 1.4.hash碰撞 1.4.1.key值的唯一性 ...

  5. 【Java源码分析】Java8的HashMap源码分析

    Java8中的HashMap源码分析 源码分析 HashMap的定义 字段属性 构造函数 hash函数 comparableClassFor,compareComparables函数 tableSiz ...

  6. JDK 1.6 HashMap 源码分析

    前言 ​ 前段时间研究了一下JDK 1.6 的 HashMap 源码,把部份重要的方法分析一下,当然HashMap中还有一些值得研究得就交给读者了,如有不正确之处还望留言指正. 准备 ​ 需要熟悉数组 ...

  7. Java类集框架 —— HashMap源码分析

    HashMap是基于Map的键值对映射表,底层是通过数组.链表.红黑树(JDK1.8加入)来实现的. HashMap结构 HashMap中存储元素,是将key和value封装成了一个Node,先以一个 ...

  8. 查询已有链表的hashmap_源码分析系列1:HashMap源码分析(基于JDK1.8)

    1.HashMap的底层实现图示 如上图所示: HashMap底层是由  数组+(链表)=(红黑树) 组成,每个存储在HashMap中的键值对都存放在一个Node节点之中,其中包含了Key-Value ...

  9. 源码分析系列1:HashMap源码分析(基于JDK1.8)

    1.HashMap的底层实现图示 如上图所示: HashMap底层是由  数组+(链表)+(红黑树) 组成,每个存储在HashMap中的键值对都存放在一个Node节点之中,其中包含了Key-Value ...

  10. 从源码分析 Spring 基于注解的事务

    从源码分析 Spring 基于注解的事务 在spring引入基于注解的事务(@Transactional)之前,我们一般都是如下这样进行拦截事务的配置: <!-- 拦截器方式配置事务 --> ...

最新文章

  1. sublime运行错误
  2. 当AI实现多任务学习,它究竟能做什么?
  3. Windows上安装Apache
  4. Visual C++ 中的重大更改
  5. hdu 1569 方格取数(2) 最大点权独立集
  6. for循环和数组练习
  7. TIOBE 9 月编程语言榜:Python 居然超越了 C++!
  8. composer 下载包慢的解决方法
  9. java java se_Java SE 9:不可变列表的工厂方法
  10. 20200728每日一句
  11. Qt基本控件及三大布局
  12. 华罗庚杯成绩查询2021高考成绩,第22届华杯赛入围决赛分数线已定,明天起可查询成绩...
  13. 2022五一劳动节虾皮仓库物流放假安排
  14. FrameMaker 格式的本地化流程
  15. js:ajax的get方法实现简单的搜索框提示
  16. 运维派 » 你有自己的Web缓存知识体系吗?
  17. Python+PyCharm的一些基本设置:安装使用、注册码、显示行号、字体大小和快捷键等常用设置...
  18. lerna import报错
  19. 推荐一个自动破解替换密码的工具
  20. 可视化工具VisIt安装使用教程(Windows)

热门文章

  1. eclipse的控制台显示有问题,关闭Limit console output
  2. 好看的流程审批html,审批流程(加班)驳回(流程被删除).html
  3. feign扫描_微服务实战SpringCloud之Feign简介及使用
  4. 计算机数据处理规模大小分为,计算机组成原理题集样稿.doc
  5. paypalsdk集成php,php核心paypal sdk
  6. 【2019银川网络赛D:】Take Your Seat(概率--递推+思维)
  7. 信用评分卡模型分析(基于Python)--理论部分
  8. MATLAB--二分法
  9. 环信 java接口实例_环信即时聊天与java后台接口对接demo
  10. 极客大学架构师训练营 JVM虚拟机原理 JVM垃圾回收原理 Java编程优化 第17课 听课总结