HashMap1.8常见面试问题
1.hashmap转红黑树的时机:
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
在treeifyBin方法中还会判断:
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
resize()
;
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
//tab为node数组,p为hash运算后的下标i的node节点,n为tab长度,i为node数组下标
Node<K,V>[] tab; Node<K,V> p; int n, i;
//hashmap采用懒加载思想,只有真正用的时候才会初始化
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//里面有个知识点 运算符的优先级 ()>&>= 所以先用长度-1和hash值与运算再赋给坐标i
if ((p = tab[i = (n - 1) & hash]) == null)
//tab[i]空直接插入
tab[i] = newNode(hash, key, value, null);
else {
//e为后面要操作的node节点 k为node的key p为已存在的下标i的node节点
Node<K,V> e; K k;
//常问的重写hashcode和equals方法答案就在下面这句话
//当p和e的hash值相同并且引用p,k相同或者key调用equals方法相同时直接覆盖
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
//将把p赋给e,后续e都是我们默认操作的put的key的节点
e = p;
else if (p instanceof TreeNode)
//如果是树则使用其他的put方法
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
//下面是链表的遍历过程
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
//尝试转红黑树
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
//找到存在的key 跳出循环
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
//上面链表遍历有3种结果 1.node链表长度小于8没有找到对应key,直接尾插法new一个node
2.node链表大于8,尝试红黑树转换
3.node链表长度小于8找到对应的key,跳出循环,此时e是有值
所以我们说e是最后我们操作存在key的node节点
//返回旧值,放入新值
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
//是否扩容
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
3.hashmap resize过程
resize总结:1.获取新的容量和临界值
2.创建新表将hashmap tab rehash 元素以原来下标或者二次幂的偏移量下标移动
/**
* Initializes or doubles table size. If null, allocates in
* accord with initial capacity target held in field threshold.
* Otherwise, because we are using power-of-two expansion, the
* elements from each bin must either stay at same index, or move
* with a power of two offset in the new table.
*
* @return the table
*/
//二次幂展开,扩容后元素要么是原有下标,要么以二次幂偏移量
//exp:假设容量为16 key的hash为16 1111&10000=0存放在tab[0]中 如果扩容后容量为32 那么rehash时11111&10000 就是tab[16] 所以以二次幂的偏移量移动
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
//oldTab==null 为了兼容初始化
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
int newCap, newThr = 0;
if (oldCap > 0) {
//当容量大于允许最大容量时临界点为Integer最大值
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
//正常扩容 临界点和容量都扩大两倍
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
}//使用临界值覆盖容量
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
else { // zero initial threshold signifies using defaults
//当oldThr是0时初始化
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
//临界值还是为0
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"})
//创建新tab
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
if (oldTab != null) {
//遍历oldTab数组
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
if ((e = oldTab[j]) != null) {
oldTab[j] = null;
if (e.next == null)
//该下标只有一个node时直接放入新tab中
newTab[e.hash & (newCap - 1)] = e;
else if (e instanceof TreeNode)
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else { // preserve order
//下面的代码分为了两个链表 其实就是把hash大于等于cap的和小于的分开 小于的在原位 大于等于的按照oldcap偏移量下标存放
//并且采用了尾插法
Node<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) {
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = 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;
}
HashMap1.8常见面试问题的更多相关文章
- Android开发面试经——6.常见面试官提问Android题②(更新中...)
版权声明:本文为寻梦-finddreams原创文章,请关注:http://blog.csdn.net/finddreams 关注finddreams博客:http://blog.csdn.net/fi ...
- Android开发面试经——5.常见面试官提问Android题①
版权声明:本文为寻梦-finddreams原创文章,请关注:http://blog.csdn.net/finddreams 关注finddreams博客: http://blog.csdn.net/f ...
- 机器学习&数据挖掘笔记(常见面试之机器学习算法思想简单梳理)
机器学习&数据挖掘笔记_16(常见面试之机器学习算法思想简单梳理) 作者:tornadomeet 出处:http://www.cnblogs.com/tornadomeet 前言: 找工作时( ...
- [转]机器学习&数据挖掘笔记_16(常见面试之机器学习算法思想简单梳理)
机器学习&数据挖掘笔记_16(常见面试之机器学习算法思想简单梳理) 转自http://www.cnblogs.com/tornadomeet/p/3395593.html 前言: 找工作时(I ...
- java异常常见面试问题
java异常常见面试问题 一.java异常的理解 异常主要是处理编译期不能捕获的错误.出现问题时能继续顺利执行下去,而不导致程序终止,确保程序的健壮性. 处理过程:产生异常状态时,如果当前的conte ...
- (转)C/C++ 程序设计员应聘常见 面试笔试 试题深入剖析
C/C++ 程序设计员应聘常见 面试笔试 试题深入剖析 http://www.nowcoder.com/discuss/1826?type=2&order=0&pos=23&p ...
- Hibernate的10个常见面试问题及答案
在Java J2EE方面进行面试时,常被问起的Hibernate面试问题,大多都是针对基于Web的企业级应用开发者的角色的.Hibernate框架在Java界的成功和高度的可接受性使得它成为了Java ...
- Android常见面试笔试题目
Android常见面试笔试题目 1.在多线程编程这块,我们经常要使用Handler,Thread和Runnable这三个类,那么他们之间的关系你是否弄清楚了呢? 答:可以处理消息循环的线程,他是一个拥 ...
- BAT网络运维常见面试题目总结
BAT常见面试题目总结 Author:Danbo 2015-7-11 TCP/IP详解鸟哥Linux的书网络安全ping的原理make的过程文件有哪些类型各种Linux发行版的区别.有关suid的作用 ...
随机推荐
- 5.Java程序运行机制
一.编译型和解释型语言区别 计算机是不能理解高级语言的,更不能直接执行高级语言,它只能直接理解机器语言,所以任何的高级语言编写的程序都必须转换成计算机语言,也就是机器码.而这种转换的方式有两种: 编译 ...
- pt-osc又又出现死锁了
今天使用pt-osc修改mysql表结构,又出现死锁了,老大让尽量解决这个问题,我们先分析一下pt-osc容易出现死锁的原因,再来解决这个问题. 根据pt-osc打印的日志,可以看到pt-osc执行原 ...
- Asynchronous Methods for Deep Reinforcement Learning
郑重声明:原文参见标题,如有侵权,请联系作者,将会撤销发布! ICML 2016 Abstract 我们提出了一个概念上简单且轻量级的深度强化学习框架,该框架使用异步梯度下降来优化深度神经网络控制器. ...
- Java语言学习day29--8月04日
今日内容介绍1.Object2.String3.StringBuilder ###01API概念 * A:API(Application Programming Interface) * 应用程序编程 ...
- 用 GraphScope 像 NetworkX 一样做图分析
NetworkX 是 Python 上最常用的图分析包,GraphScoep 兼容 NetworkX 接口.本文中我们将分享如何用 GraphScope 像 NetworkX 一样在(大)图上进行分析 ...
- Mybatis Plus之内置Mapper实践
MyBatis Plus,作为对MyBatis的进一步增强,大大简化了我们的开发流程,提高了开发速度 配置 由于Mybatis Plus是建立在Mybatis之上的,所以其已经依赖了Mybatis,故 ...
- CS基础课不完全自学指南
本文讲的是计算机学生怎么自学专业课,说长点就是该如何借助网络上已有的高质量学习资源(主要是公开课)来系统性的来点亮自己的CS技能树.这篇文章完全就是一篇自学性质的指南,需要对编程充满热情,起码觉得编程 ...
- C++实例2--职工管理系统
职工管理系统 1. 头文件 1.1 workerManager.h 系统类 1 #pragma once // 防止头文件重复包含 2 #include<iostream> // 包含输 ...
- 手把手教你在Linux中快速检测端口的 3 个小技巧
一个执着于技术的公众号 前言 无论是要解决网络连接问题还是配置防火墙,第一件事是要检查系统实际打开了哪些端口. 本文介绍了几种快速查找 Linux 系统上哪些端口向外部开放的方法. 什么是开放端口 监 ...
- Blazor和Vue对比学习(基础1.2):模板语法和Razor语法
Vue使用模板语法,Blazor使用祖传的Razor语法,从逻辑和方向上看,两者极为相似,比如: 都基于HTML 都通过声明式地将组件实例的状态(数据/方法)绑定到呈现的DOM上 都通过指令实现更加丰 ...