如何进行HashMap扩容机制源码分析

技术如何进行HashMap扩容机制源码分析这期内容当中小编将会给大家带来有关如何进行HashMap扩容机制源码分析,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。具体看源码之前,我们先简

本期,边肖将为大家带来关于如何分析HashMap扩展机制源代码的信息。文章内容丰富,从专业角度进行分析和描述。看完这篇文章,希望你能有所收获。

在详细看源代码之前,我们先简单说一下HashMap的底层数据结构。

1.HashMap底部的数据结构是一个数组链表红黑树。

2.我们首先需要了解HashMap底部的两个变量。

2-1:负载系数:默认值为0.75,这是反复测试后最合适的值。

2-2-2:阈值:当地图中的数据大于此阈值时,容量将会扩大。

注:了解有关负载系数的更多信息,请单击此处。

2.现在我们来看看HashMap的构造方法。

HashMap可以通过四种方式构建。

1.空参数构造方法。此时,加载因子默认为0.75,不会创建任何空间。

阈值为0

数组为空。

public HashMap(){ 0

this . LoAdfactor=DEFAULT _ LoAd _ FACTOR;//默认所有其他字段

}

2.给定初始容量,这个构造函数将直接调用第三个构造函数。

阈值已经有值

数组为空。

公共HashMap(int initial capacity){ 0

这个(initialCapacity,DEFAULT _ LOAD _ FACTOR);

}

3.给定初始化大小和加载因子。

1.实际上,不建议修改默认加载因子。当然,除非你很了解逻辑,并且找到了适合你的项目的负载系数。

2.首先判断你给的初始化容量是否合法,如果合法,就用这个初始化容量来计算阈值。

阈值已经有值

数组为空。

公共HashMap(int initialCapacity,float load factor){ 0

if(初始电容0)

引发新的IllegalArgumentException('非法的初始容量: '

initial capacity);

if(initial CAPACITY max _ CAPACITY)

initial CAPACITY=max _ CAPACITY;

if(load factor=0 | | float . isnan(load factor))

引发新的IllegalArgumentException('非法加载因子: '

load FactOr);

this.loadFactor=loadFactor

this . threshold=tablesize for(initial capacity);

}

4.将贴图作为参数传递,加载因子将适应默认值0.75。将其他地图转换为HashMap

阈值已经有值

数组也不是空的

公共HashMap(地图m){ 0

this . LoAdfactor=DEFAULT _ LoAd _ FACTOR;

putMapEntries(m,false);

}

最终无效putMapEntries(Map m,布尔驱逐){ 0

int s=m . size();

if(s)0

if (table==null) { //pre-size

浮子ft=((浮子)s/LoAdFactor)1.0F;

int t=((ft(float)max _ CAPACITY)?

(int)ft : max _ CAPACITY);

if (t阈值)

threshold=tablesize for(t);

}

否则如果阈值

resize();

(地图。条目e :m . EntrySet()){ 0

k key=e . GetKey();

v value=e . GetValue();

putVal(hash(key),key,value,false,驱逐);

>

  }

  }

  }

  3、让我们看看是HashMap是怎么进入扩容的

  3-1:我们先从 put() 这个方法说起

  public V put(K key, V value) {

  return putVal(hash(key), key, value, false, true);

  }

  这个put方法底层是调用了一个叫 putVal 的方法,但是在这之前我们有必要看一下hash()这个方法。

  直接使用 对象.hashCode(), 可能会出现重复,所以这个hash是对生成的hashcode进行一下扰乱,让其重复性更低。

  从这里也可以看到,HashMap只允许一个null键

  static final int hash(Object key) {

  int h;

  return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);

  }

  3-2:下面我们看一下这个putVal方法

  putVal源码:

  final V putVal(int hash, K key, V value, boolean onlyIfAbsent,

  boolean evict) {

  Node[] tab; Node p; int n, i;

  if ((tab = table) == null || (n = tab.length) == 0)

  n = (tab = resize()).length;

  if ((p = tab[i = (n - 1) & hash]) == null)

  tab[i] = newNode(hash, key, value, null);

  else {

  Node e; K k;

  if (p.hash == hash &&

  ((k = p.key) == key || (key != null && key.equals(k))))

  e = p;

  else if (p instanceof TreeNode)

  e = ((TreeNode)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;

  }

  if (e.hash == hash &&

  ((k = e.key) == key || (key != null && key.equals(k))))

  break;

  p = e;

  }

  }

  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;

  }

  这个源码看起来还是有点复杂的,考虑到很多同学可能和我一样数据结构并不是太好。我把它简化一下,提取里面的思想便于理解

  final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean
evict) {

  Node[] tab; Node p; int n, i;

  if ((tab = table) == null || (n = tab.length) == 0){

  // 当数据为null或者长度为0的时候进行扩,并发扩后的长度返回给n(前面说了hashMap底层最开始是个数组)

  n = (tab = resize()).length;

  }

  // 之前可能有同学有疑问,hashcode那么长,为啥默认HashMap数组默认长度是16。其实最后的下标是经过处理的 (n - 1) &
hash

  if ((p = tab[i = (n - 1) & hash]) == null){

  // 如果当前数组的下标,并没有数据,也就是说当前添加的数据是第一个,那就直接加入进去就好了。不需要排序啥的

  tab[i] = newNode(hash, key, value, null);

  }

  else {

  // 找到了数据下标,并且里面的已经有数据了,

  // 这里就要找到当前数据的位置属于那里并加入进去,

  // 还要判断当前长度是否大于我们设置的长度,大于就要把链转化成红黑树便于查找

  }

  ++modCount;

  // 判断当前长度是否大于需要扩的长度,其实也好理解,数组是可以装满的,但是链不可能满呀,但是长度超过一定的长度的时候链的性能就会很差了

  if (++size > threshold)

  resize();

  // 节点插入后的操作,目前这个没有任何实现,里面是个空方法

  afterNodeInsertion(evict);

  return null;

  }

  3-3:总结进入扩容的两种情况

  添加一个数据的时候,底层数组为空的时候

  添加一个数据结束后,判断当前数据个数是否大于threshold (需要扩容)的大小,大于就进行扩容

  注:因为数据是具体添加到数组里面的链表,所以不存在数组越界情况。

  4、具体看一下扩容代码

  扩容源码:

  final Node[] resize() {

  Node[] 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; // double threshold

  }

  else if (oldThr > 0) // initial capacity was placed in threshold

  newCap = oldThr;

  else { // zero initial threshold signifies using defaults

  newCap = DEFAULT_INITIAL_CAPACITY;

  newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);

  }郑州专业妇科医院 http://fk.zyfuke.com/

  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"})

  Node[] newTab = (Node[])new Node[newCap];

  table = newTab;

  if (oldTab != null) {

  for (int j = 0; j < oldCap; ++j) {

  Node e;

  if ((e = oldTab[j]) != null) {

  oldTab[j] = null;

  if (e.next == null)

  newTab[e.hash & (newCap - 1)] = e;

  else if (e instanceof TreeNode)

  ((TreeNode)e).split(this, newTab, j, oldCap);

  else { // preserve order

  Node loHead = null, loTail = null;

  Node hiHead = null, hiTail = null;

  Node 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;

  }

  同样的把源码进行一下简单的分享,去除复杂的内容

  // 这个扩容方法就是

  // 1、找到新的容量大小和新的threshold大小

  // 2、把旧的数据全部复制到新的数组中去

  final Node[] resize() {

  Node[] 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; // double threshold

  }

  // 使用初始化容量或初始化容量+初始化加载因子参数的构造方法,第一次进入扩容

  else if (oldThr > 0){

  newCap = oldThr;

  }

  // 使用空参构造方法第一次扩容进入,使用参数为map的构造方法,第一次也会进入这个扩容方法

  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"})

  Node[] newTab = (Node[])new Node[newCap];

  table = newTab;

  // 把旧的数据全部复制到新的数组中去

  if (oldTab != null) {

  //

  }

  return newTab;

  }

  5、总结(面试的时候:请你说一下HashMap的扩容):

  HashMap底层数据结构是 数组 + 链表 + 红黑树

  真正的数据是存储在链表中的,链表的长度是无限的。所以这时候就引入一个变量 threshold

  当第一次向map里面添加数据,或添加完数据后的大小,大于 threshold的大小,这时候就会进行扩容

  先说一下非第一次扩容,这个相对简单点

  1、如果当前的容量大小,大于等于HashMap规定的最大容量的话,直接让threshold等于Integer的最大值,就可以了。

  2、一般情况当前数组长度是不会大于最大值的,所以这时候新的数组长度等于旧数组的2倍。如果新的数组长度小于HashMap规定的最大值,并且旧的数组长度也大于等于HashMap规定的默认大小容量大小(16),那么threshold扩大2倍,否则不变

  非第一次扩容

  1、HashMap,有四个构造方法。空参构造方法的threshold变量是0,其它构造方法threshold都有初始值。

  2、当旧的threshold大于0的时候,新的数组容量大小就等于旧的threshold大小。新的threshold大小等于加载因子新的数组大小。

  3、当旧的threshold不大于0的时候,新的数组大小就等于默认的大小(16),新的threshold大小,就等于默认的容量大小默认的加载因子大小

上述就是小编为大家分享的如何进行HashMap扩容机制源码分析了,如果刚好有类似的疑惑,不妨参照上述分析进行理解。如果想知道更多相关知识,欢迎关注行业资讯频道。

内容来源网络,如有侵权,联系删除,本文地址:https://www.230890.com/zhan/138376.html

(0)

相关推荐

  • JS中的offsetwidth有什么作用

    技术JS中的offsetwidth有什么作用本篇内容介绍了“JS中的offsetwidth有什么作用”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大

    攻略 2021年11月12日
  • MySql支持哪些索引类型

    技术MySql支持哪些索引类型这篇文章主要讲解了“MySql支持哪些索引类型”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“MySql支持哪些索引类型”吧!一、什么是索引?

    攻略 2021年12月4日
  • 软件设计-策略模式

    技术软件设计-策略模式 软件设计-策略模式旅行方式的选择旅游的出行方式有乘坐飞机旅行、乘火车旅行和自行车游,不同的旅游方式有不同的实现过程,客户可以根据自己的需要选择一种合适的旅行方式。类图Javapa

    礼包 2021年12月14日
  • oracle merge into用法(oracle awr性能分析报告)

    技术Oracle sharding database的示例分析这篇文章主要为大家展示了“Oracle sharding database的示例分析”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一

    攻略 2021年12月13日
  • 怎么理解python的第三方库pandas

    技术怎么理解python的第三方库pandas本篇内容介绍了“怎么理解python的第三方库pandas”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希

    攻略 2021年11月10日
  • python写自动化脚本用什么框架(python自动化脚本如何开展)

    技术Python的自动化脚本有哪些Python的自动化脚本有哪些,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。前言:大家平时有没有注意到你每

    攻略 2021年12月20日