阿里面试题:说说HashMap的扩容过程?
这是一道阿里的面试题,考察你对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的扩容过程?的更多相关文章
- Java HashMap的扩容
		
最近博主参加面试,发现自己对于Java的HashMap的扩容过程理解不足,故最近在此进行总结. 首先说明博主德Java为1.8版本 HashMap中的变量 首先要了解HashMap的扩容过程,我们就得 ...
 - Java面试题之HashMap阿里面试必问知识点,你会吗?
		
面试官Q1:你用过HashMap,你能跟我说说它的数据结构吗? HashMap作为一种容器类型,无论你是否了解过其内部的实现原理,它的大名已经频频出现在各种互联网Java面试题中了.从基本的使用角度来 ...
 - 透过面试题掌握HashMap【持续更新中】
		
本文主要是自己阅读了HashMap和ConcurrentHashMap源码及一些Java容器类相关的博客后,找了一些很多面经中涉及到的Java容器相关的面试题,自己全部手写的解答,也花了一些流程图,之 ...
 - 深入理解HashMap的扩容机制
		
什么时候扩容: 网上总结的会有很多,但大多都总结的不够完整或者不够准确.大多数可能值说了满足我下面条件一的情况. 扩容必须满足两个条件: 1. 存放新值的时候当前已有元素的个数必须大于等于阈值 2. ...
 - HashMap的扩容机制---resize()
		
虽然在hashmap的原理里面有这段,但是这个单独拿出来讲rehash或者resize()也是极好的. 什么时候扩容:当向容器添加元素的时候,会判断当前容器的元素个数,如果大于等于阈值---即当前数组 ...
 - HashMap的扩容机制以及默认大小为何是2次幂
		
HashMap的Put方法 回顾HashMap的put(Key k, Value v)过程: (1)对 Key求Hash值,对n-1取模计算出Hash表数组下标 (2)如果没有碰撞,直接放入桶中,即H ...
 - HashMap初始化容量过程
		
集合是Java开发日常开发中经常会使用到的,而作为一种典型的K-V结构的数据结构,HashMap对于Java开发者一定不陌生.在日常开发中,我们经常会像如下方式以下创建一个HashMap: Map&l ...
 - 双系统Ubuntu分区扩容过程记录
		
本人电脑上安装了Win10 + Ubuntu 12.04双系统.前段时间因为在Ubuntu上做项目要安装一个比较大的软件,导致Ubuntu根分区的空间不够了.于是,从硬盘又分出来一部分空间,分给Ubu ...
 - jdk7和8中关于HashMap和concurrentHashMap的扩容过程总结,以及HashMap死循环
		
题外话:为什么要hashcode进行spread? 充分使用key.hashCode()的高16位信息,保证hash分布更分散, 扩容操作是新建2倍于原表大小的新表,并将原表结点拷贝一份放在新表中,对 ...
 
随机推荐
- 文本框复制代码,兼容大部分浏览器(ZeroClipboard插件、附件)
			
;;list-style-type:none;} a,img{;} body{font:12px/180% Arial, Helvetica, sans-serif ,"新宋体"; ...
 - nginx的工作流程
			
nginx请求处理流程 nginx进程结构 master进程:是作为worker进程管理的 worker进程:处理真正的请求的而master进程则是管控这些进程的工作方式的:缓存是在多个worker进 ...
 - javaweb基础(24)_jsp一般的标签开发
			
一.标签技术的API 1.1.标签技术的API类继承关系 二.标签API简单介绍 2.1.JspTag接口 JspTag接口是所有自定义标签的父接口,它是JSP2.0中新定义的一个标记接口,没有任何属 ...
 - java,求1-100之和。
			
package study01; public class TestWhile { public static void main(String[] args) { int sum = 0; int ...
 - 约束Constraints
			
1.setNeedsUpdateConstraints:当想要调整子视图布局时,在主线程调用该方法标记constraint需要在未来的某个点更新(该方法不会立刻强制刷新constraint,而是等待下 ...
 - JavaScript之基操
			
局部变量前面要加var 如 var name = "jiahuai" 全局变量 name = "jiahuai" 写完每一行JavaScript代码用;号隔 ...
 - 分享几个能用的 editplus 注册码
			
转载自: https://www.cnblogs.com/shihaiming/p/6422441.html 原文:http://host.zzidc.com/wljc/1286.html EditP ...
 - ubuntu 设置定时任务
			
crontab -l #查看详情crontab -e #设置定时任务 * * * * * command 分 时 日 月 周 命令 第1列表示分钟1-59 每分钟用*或者 */1表示 第2列表示小时 ...
 - 循环字典进行操作时出现:RuntimeError: dictionary changed size during iteration的解决方案
			
在做对员工信息增删改查这个作业时,有一个需求是通过用户输入的id删除用户信息.我把用户信息从文件提取出来储存在了字典里,其中key是用户id,value是用户的其他信息.在循环字典的时候,当用户id和 ...
 - writing  a  usb driver(在国外的网站上复制下来的)
			
Writing a Simple USB Driver From Issue #120April 2004 Apr 01, 2004 By Greg Kroah-Hartman in Soft ...