【你问我答】hashmap什么时候会触发扩容?

问题描述:

hashmap什么时候会触发扩容?

回答有奖:

选取一位认真回答问题的牛友,赠送200牛币!
▶回答尽量有自己的思考,不要单纯的只是复制粘贴定理定义,或者他人blog哦~

你问我答问题汇总:点击进入
关注你问我答栏目:点击关注

你问我答 - 答问题,成大佬,拿牛币!
你问我答是牛客新栏目,每周1期几个面试中真实遇到的问题,
牛友在问题贴下留下自己的知识,经验与见解,
帮助更多牛友了解更多技术相关知识!

#悬赏##Java##面试题目#
全部评论
提问:hashmap什么时候会触发扩容? 回答:两种情况(基于JDK1.8) 1. 当HashMap中元素总个数达到阈值时就会扩容。注意是元素总个数,而不是数组占用个数。 // 数组扩容阈值,即:HashMap数组总容量 * 负载因子 int threshold // 如果元素个数大于阈值,扩充数组。  if (++size > threshold)       resize(); 2. 比较复杂,当向HashMap中添加元素时,即调用  final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) 方法时,如果通过hash值计算数组的索引,获取该索引位的首节点,且该首节点不为null时(即发生了Hash碰撞),会有对应三种处理情况:  ① 新添加元素的key与首节点元素的key相同,即 // 如果首节点的key和要存入的key相同,那么直接覆盖value的值。 // p:hash值对应索引位置的首节点  if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))      e = p; ② 如果首节点是红黑树节点(TreeNode),将键值对添加到红黑树。 // 如果首节点是红黑树的,将键值对插添加到红黑树  else if (p instanceof HashMap.TreeNode)      e = ((HashMap.TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);  ③ 如果首节点是链表,将键值对添加到链表。添加之后会判断链表长度是否到达TREEIFY_THRESHOLD - 1这个阈值,“尝试”将链表转换成红黑树。 // p.next == null,到达链表末尾,添加新节点,如果长度足够,转换成树结构。  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()方法中,如果  // 把链表转换为红黑色  final void treeifyBin(Node<K,V>[] tab, int hash) {      int n, index; Node<K,V> e;  // 如果当前数组容量太小(小于64),放弃转换,扩充数组。       if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY) {           resize();       } else if ((e = tab[index = (n - 1) & hash]) != null) {           // 将链表转成红黑树...      }  } HashMap在jdk1.8之后引入了红黑树的概念,表示若桶中链表元素超过8时,会自动转化成红黑树;若桶中元素小于等于6时,树结构还原成链表形式。而如果当前数组的容量太小(小于64),则放弃转换,扩充数组。扩充数组!扩充数组!扩充数组! 这样就有可能有一种情况出现,就是说我整个HashMap并没有达到负载因子那么多的元素个数,但是,我进行了一次扩容。所以单纯的以负载因子来看是否会发生扩容,是不全面的。  那我们来分析为什么要进行这一次扩容。  首先我们要理解HashMap为什么要进行扩容,扩容这个操作真正的意义在哪里?为什么要定义一个负载因子?设计他的初衷究竟是为了解决什么问题?  为什么要扩容呢,之所以要扩容,我理解是基于两个逻辑,  1. HashMap中元素个数实在太多了,已经很频繁的发生hash冲突,我不得不进行扩容来减少这个冲突,来增强效率。  2. HashMap中元素并不多,但元素都集中到那么一两个“坑位”上,数组占用的位置很少,但是在那一两个位置上的元素已经很多了,拖掉了执行的效率。即核心是一个分布不均匀的问题,在这样的情况下,扩容的目的,就是通过重新计算hash“坑位”让原来聚集在一起的节点分散开来,是为了让分布更加均匀。  而treeifyBin()方法中,之所以判断当前数组容量是否太小,也是基于第二个逻辑上的考量,因为数组容量很小,但是已经存在过长的链表需要转换成树结构,不如直接进行一次扩容,毕竟树的查询效率O(logn)也比不上一个正常HashMap的O(1)的效率。
8
送花
回复
分享
发布于 2020-02-11 17:41
hashmap中有一个参数叫做负载因子,代表当hashmap容量使用率达到一定比率时触发扩容。1.8版本后hashmap由数组加链表和红黑树组成。数组的每个元素是一个链表或者红黑树的根节点。对于数组元素下是链表还是红黑树是由对应数据结构深度决定,当链表深度超过8会转换成红黑树,当红黑树深度小于3会退化成链表。其实这样的转换是为了提高查询效率。那么其实对于负载因子的计算就是判断数组中有多少元素被使用。当使用比率超过负载因子就会触发扩容。更详细的源码解读见我的github地址,github.com/dncba/Learn-More-Do-Less 中jdk源码解读相关
1
送花
回复
分享
发布于 2020-02-11 11:57
网易互娱
校招火热招聘中
官网直投
jdk8中是当插入的数据个数大于容量*负载因子
点赞
送花
回复
分享
发布于 2020-02-11 13:33
桶中已经使用的个数大于桶的装载因子时
点赞
送花
回复
分享
发布于 2020-02-11 14:04

相关推荐

3 4 评论
分享
牛客网
牛客企业服务