这是一道阿里的面试题,考察你对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. jvm | 基于栈的解释器执行过程

    一段简单的算术代码: public class Demo { public static void main(String[] args) { int a = 1; int b = 2; int c ...

  2. C11 C语言文件的读写

    目录 文件的打开和关闭 字符流读写文件 文件的打开和关闭 fopen( ) fopen( ) 函数来创建一个新的文件或者打开一个已有的文件,这个调用会初始化类型 FILE 的一个对象,类型 FILE ...

  3. Linux curl 详解

    Linux下载工具Curl也是Linux下不错的命令行下载工具,小巧.高速,唯一的缺点是不支持多线程下载.以下是他的安装和功能. 安装 $ tar zxvf curl-7.14.0.tar.gz $ ...

  4. 从屏幕截取一块区域,将其赋给imageView

    UIGraphicsBeginImageContext(self.bounds.size); [self.layerrenderInContext:UIGraphicsGetCurrentContex ...

  5. deque 用法

    引用博客:https://blog.csdn.net/zyq522376829/article/details/46801973 下面是那位大佬写的的笔记整理~~~~ deque - 双向队列 1.构 ...

  6. R-data.table

    data.table可以扩展和增强data.frame的功能,在分组操作和组合时访问速度更快. require(data.table) theDT = data.table(A=1:10, B=let ...

  7. LeetCode(215) Kth Largest Element in an Array

    题目 Find the kth largest element in an unsorted array. Note that it is the kth largest element in the ...

  8. 查看 EGLIBC 版本

    $ ldd --versionldd (Debian EGLIBC 2.13-38+deb7u1) 2.13

  9. selenium2截图ScreenShot的使用

    截图是做测试的基本技能,在有BUG的地方,截个图,保留失败的证据,也方便去重现BUG.所以,在自动化的过程中,也要能截图,也要能在我们想要截取的地方去截图,且能在错误产生时,自动的截图. 示例: 脚本 ...

  10. c++ dll 创建

    建立一个C++的Win32DLL,这里要注意选择"Export symbols"导出符号.点击完成. 如下图所示:   由于项目的名称是"TestCPPDLL" ...