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的作用 ...
随机推荐
- flex布局 一行4个元素 后面不够4个元素对齐
html 父元素 .container { display: flex; flex-wrap: wrap;} 子元素.list { width: 24%; height: 100px; backgro ...
- 关于Mysql索引的数据结构
索引的数据结构 1.为什么使用索引 概念: 索引是存储索引用于快速找到数据记录的一种数据结构,就好比一本书的目录部分,通过目录中对应的文章的页码,便可以快速定位到需要的文章,Mysql 中也是一样的道 ...
- GO语言学习——切片三 append()方法为切片添加元素、切片的扩容策略、使用copy()函数复制切片、从切片中删除元素
append()方法为切片添加元素 注意:通过var声明的零值切片可以在append()函数直接使用,无需初始化. var s []int s = append(s, 1, 2, 3) append( ...
- C# 有关List<T>的Contains与Equals方法
[以下内容仅为本人在学习中的所感所想,本人水平有限目前尚处学习阶段,如有错误及不妥之处还请各位大佬指正,请谅解,谢谢!] !!!观前提醒!!! [本文内容可能较为复杂,虽然我已经以较为清晰的方式展 ...
- kill -9 进程杀不掉,怎么办?
关注「开源Linux」,选择"设为星标" 回复「学习」,有我为您特别筛选的学习资料~ 用ps和grep命令寻找僵尸进程 ps -A -ostat,ppid,pid,cmd | gr ...
- 为什么 IPv6 难以取代 IPv4
网络层协议承担了分组(Packet)转发和路由选择两大功能,它能够为上层提供在不同主机之间运输分组的职责,IP 协议作为网络层协议,它虽然只能提供无连接的.不可靠的服务,但是它在今天的互联网中起到了极 ...
- Flutter异步与线程详解
一:前言 - 关于多线程与异步 关于 Dart,我相信大家都知道Dart是一门单线程语言,这里说的单线程并不是说Dart没有或着不能使用多线程,而是Dart的所有API默认情况下都是单线程的.但大家也 ...
- Oracle19c单实例数据库配置OGG单用户数据同步测试
目录 19c单实例配置GoldenGate 并进行用户数据同步测试 一.数据库操作 1.开启数据库附加日志 2.开启数据库归档模式 3.开启goldengate同步 4.创建goldengate管理用 ...
- 基于DEM的坡度坡向分析
坡度坡向分析方法 坡度(slope)是地面特定区域高度变化比率的量度.坡度的表示方法有百分比法.度数法.密位法和分数法四种,其中以百分比法和度数法较为常用.本文计算的为坡度百分比数据.如当角度为45度 ...
- 04 Springboot 格式化LocalDateTime
Springboot 格式化LocalDateTime 我们知道在springboot中有默认的json解析器,Spring Boot 中默认使用的 Json 解析技术框架是 jackson.我们点开 ...