这是一道阿里的面试题,考察你对HashMap源码的了解情况,废话不多说,咱们就直接上源码吧!

jdk 1.7 源码

void resize(int newCapacity) {
Entry[] oldTable = table;//保存旧数组
int oldCapacity = oldTable.length;
if (oldCapacity == MAXIMUM_CAPACITY) {//判断当前数组大小是否达到最大值
threshold = Integer.MAX_VALUE;
return;
} Entry[] newTable = new Entry[newCapacity];//创建一个新数组
boolean oldAltHashing = useAltHashing;
useAltHashing |= sun.misc.VM.isBooted() &&
(newCapacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
boolean rehash = oldAltHashing ^ useAltHashing;//是否需要重新计算hash值
transfer(newTable, rehash);//将oldTable的元素迁移到newTable
table = newTable;//将新数组重新赋值
//重新计算阈值
threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}
void transfer(Entry[] newTable, boolean rehash) {
int newCapacity = newTable.length;
for (Entry<K,V> e : table) {//遍历oldTable迁移元素到newTable
while(null != e) {//①处会导致闭环,从而导致e永远不为空,然后死循环,内存直接爆了
Entry<K,V> next = e.next;
if (rehash) {//是否需要重新计算hash值
e.hash = null == e.key ? 0 : hash(e.key);
}
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];//①
newTable[i] = e;//①
e = next;//①
}
}
}

jdk 1.8 源码(比较长,慢慢品哈)

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;
}
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; //扩容两倍的阈值
}
else if (oldThr > 0) // 初始化新的数组大小
newCap = oldThr;
else {//上面条件都不满足,则使用默认值
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;//将新阈值赋值到当前对象
@SuppressWarnings({"rawtypes","unchecked"})
//创建一个newCap大小的数组Node
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;
if ((e = oldTab[j]) != null) {
oldTab[j] = null;//释放空间
if (e.next == null)
//如果旧数组中e后面没有元素,则直接计算新数组的位置存放
newTab[e.hash & (newCap - 1)] = e;
else if (e instanceof TreeNode)//如果是红黑树则单独处理
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else { //链表结构逻辑,解决hash冲突
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;
}
//原索引+oldCap放入数组中
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;//jdk1.8优化的点
}
}
}
}
}
return newTab;
}

总结

  jdk1.7扩容是重新计算hash;jdk1.8是要看看原来的hash值新增的那个bit是1还是0好了,如果是0则索引没变,如果是1则索引变成"原索引+oldCap".这是jdk1.8的亮点,设计的确实非常的巧妙,即省去了重新计算hash值得时间,又均匀的把之前的冲突的节点分散到新的数组bucket上

  jdk1.7在rehash的时候,旧链表迁移到新链表的时候,如果在新表的数组索引位置相同,则链表元素会倒置,但是jdk1.8不会倒置

阿里面试题:说说HashMap的扩容过程?的更多相关文章

  1. Java HashMap的扩容

    最近博主参加面试,发现自己对于Java的HashMap的扩容过程理解不足,故最近在此进行总结. 首先说明博主德Java为1.8版本 HashMap中的变量 首先要了解HashMap的扩容过程,我们就得 ...

  2. Java面试题之HashMap阿里面试必问知识点,你会吗?

    面试官Q1:你用过HashMap,你能跟我说说它的数据结构吗? HashMap作为一种容器类型,无论你是否了解过其内部的实现原理,它的大名已经频频出现在各种互联网Java面试题中了.从基本的使用角度来 ...

  3. 透过面试题掌握HashMap【持续更新中】

    本文主要是自己阅读了HashMap和ConcurrentHashMap源码及一些Java容器类相关的博客后,找了一些很多面经中涉及到的Java容器相关的面试题,自己全部手写的解答,也花了一些流程图,之 ...

  4. 深入理解HashMap的扩容机制

    什么时候扩容: 网上总结的会有很多,但大多都总结的不够完整或者不够准确.大多数可能值说了满足我下面条件一的情况. 扩容必须满足两个条件: 1. 存放新值的时候当前已有元素的个数必须大于等于阈值 2. ...

  5. HashMap的扩容机制---resize()

    虽然在hashmap的原理里面有这段,但是这个单独拿出来讲rehash或者resize()也是极好的. 什么时候扩容:当向容器添加元素的时候,会判断当前容器的元素个数,如果大于等于阈值---即当前数组 ...

  6. HashMap的扩容机制以及默认大小为何是2次幂

    HashMap的Put方法 回顾HashMap的put(Key k, Value v)过程: (1)对 Key求Hash值,对n-1取模计算出Hash表数组下标 (2)如果没有碰撞,直接放入桶中,即H ...

  7. HashMap初始化容量过程

    集合是Java开发日常开发中经常会使用到的,而作为一种典型的K-V结构的数据结构,HashMap对于Java开发者一定不陌生.在日常开发中,我们经常会像如下方式以下创建一个HashMap: Map&l ...

  8. 双系统Ubuntu分区扩容过程记录

    本人电脑上安装了Win10 + Ubuntu 12.04双系统.前段时间因为在Ubuntu上做项目要安装一个比较大的软件,导致Ubuntu根分区的空间不够了.于是,从硬盘又分出来一部分空间,分给Ubu ...

  9. jdk7和8中关于HashMap和concurrentHashMap的扩容过程总结,以及HashMap死循环

    题外话:为什么要hashcode进行spread? 充分使用key.hashCode()的高16位信息,保证hash分布更分散, 扩容操作是新建2倍于原表大小的新表,并将原表结点拷贝一份放在新表中,对 ...

随机推荐

  1. OO作业第二单元总结

    目录 一.设计策略 1 2 3 二.程序分析 1 2 3 S.O.L.I.D分析 三.Bug分析 1 2 3 四.互测策略 五.心得体会 一.设计策略 1 第一次完成的是一个傻瓜电梯,简单来说,就是来 ...

  2. IDEA安装及破解

    一.下载(IDEA 2019.1.2) 1.下载地址:https://www.jetbrains.com/idea/download/#section=windows 2.选择版本,并选择最终版(.e ...

  3. Js笔记-第17课

    课 // 作业 //深度拷贝 var obj = { name:'rong', age:'25', card:['visa','alipay'], nam :['1','2','3','4','4'] ...

  4. 01_11_Strtus2简单数据验证

    01_11_Strtus2简单数据验证 1. 引入struts标签 <%@taglib uri="/struts-tags" prefix="s" %&g ...

  5. bash编程之case语句,函数

    bash脚本编程:之case语句   条件测试: 0: 成功 1-255: 失败   命令: [ expression ] [[ expression ]] test expression   exP ...

  6. C#基础-字符串

    字符串比较,strA.CompareTo(strB) A大于B 正数 A小于B 负数 A等于B 0 string strA = "ab"; string strB = " ...

  7. python 取余运算

    python中取余运算逻辑如下: 如果a 与d 是整数,d 非零,那么余数 r 满足这样的关系: a = qd + r , q 为整数,且0 ≤ |r| < |d|. 经过测试可发现,pytho ...

  8. V4L2使用V4L2_MEMORY_USERPTR和V4L2_MEMORY_MMAP的区别

    视频应用可以通过两种方式从V4L2驱动申请buffer 1. USERPTR, 顾名思义是用户空间指针的意思,应用层负责分配需要的内存空间,然后以指针的形式传递给V4L2驱动层,V4L2驱动会把cap ...

  9. 关于C#Debug和Release

    在程序调试时的debug和release 网上有如下的描述:Debug 通常称为调试版本,它包含调试信息,并且不作任何优化,便于程序员调试程序.Release 称为发布版本,它往往是进行了各种优化,使 ...

  10. Linux磁盘简介

    为一台服务器添加三块磁盘的体系结构情况 Linux系统中用free  -l 查看磁盘的状况 说明:   1)buffers为写入缓冲区,sync将缓冲区数据写入磁盘,cache为读出缓存. 2)cac ...