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的作用 ...
随机推荐
- for .. range中的坑
最近在开发中使用了for range来遍历一个slice,结果在测试的时候遇到了bug,最后定位是错误使用for range造成的,这里记录一下: func redisSlaveScanBigKeys ...
- jq大体架构。先记录再慢慢剖析
//工具方法 Utilities //回调函数列表 Callbacks Object //异步队列 Deferred Object //浏览器功能测试 Support //数据缓存 Data //队列 ...
- python基础练习题(题目 模仿静态变量的用法)
day27 --------------------------------------------------------------- 实例041:类的方法与变量 题目 模仿静态变量的用法. 程序 ...
- CoAP调试工具Mozi.IoT.CoAP应用详解
前言 CoAP是一种类HTTP协议的物联网专用协议,其数据包为人类不可阅读的字节流形式,在开发相关应用的时候往往不能准确的了解数据包的内容.故需要专用的调试工具对数据和通讯进行调试.这篇文章是为了让开 ...
- 4.27-Postman和JMeter总结及实战描述
一.数据格式 常用的请求方法有8种,但是最常用的有4-5种 1.GET 获取资源 2.POST 添加资源(对服务端已存在的资源也可以做修改和删除操作) 3.PUT 修改资源 4 .DELETE删除资源 ...
- FreeRTOS --(15)信号量之概述
转载自 https://blog.csdn.net/zhoutaopower/article/details/107359095 在裸机编程中这样使用过一个变量:用于标记某个事件是否发生,或者标志一下 ...
- Azure Terraform(十一)Azure DevOps Pipeline 内的动态临时变量的使用
思路浅析 在我们分析的 Azure Terraform 系列文中有介绍到关于 Terraform 的状态文件远程存储的问题,我们在 Azure DevOps Pipeline 的 Task Job ...
- 《手把手教你》系列基础篇(九十六)-java+ selenium自动化测试-框架之设计篇-跨浏览器(详解教程)
1.简介 从这一篇开始介绍和分享Java+Selenium+POM的简单自动化测试框架设计.第一个设计点,就是支持跨浏览器测试. 宏哥自己认为的支持跨浏览器测试就是:同一个测试用例,支持用不同浏览器去 ...
- line-height和height关系
如图所示,line-height = font-size + 上下本行距.上下半行距总是相等.font-size居于中间.当font-size值固定时,line-height越大,半行距越大.所以当l ...
- 445. Add Two Numbers II - LeetCode
Question 445. Add Two Numbers II Solution 题目大意:两个列表相加 思路:构造两个栈,两个列表的数依次入栈,再出栈的时候计算其和作为返回链表的一个节点 Java ...