如何进行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)

相关推荐

  • 分数解方程,5年级分数解方程该怎样做

    技术分数解方程,5年级分数解方程该怎样做一分数解方程、可以根据等式的性质来解方程。1.等式性质一:等式两边都加上(或减去)同一个数,等式仍然成立。比如: x+5=15
    解:x+5 —5=15—5
    x=10
    2.等式性质

    生活 2021年10月29日
  • Java 线程池中的线程复用是如何实现的

    技术Java 线程池中的线程复用是如何实现的Java 线程池中的线程复用是如何实现的,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。前几天,技术群里有个群

    2021年11月15日
  • 英语小品,介绍个好玩的英语小品剧本

    技术英语小品,介绍个好玩的英语小品剧本Three Little Pigs 三只小猪 Storyteller: There are three little pigs living with 讲故事者英语小品:三只小猪和

    生活 2021年10月23日
  • 两个虫子,两个虫子的头身子是弹簧打一成语

    技术两个虫子,两个虫子的头身子是弹簧打一成语能屈能伸解题过程:左边鸟的弹簧缩回去就是能屈,右边鸟的弹簧拉起来就是能伸,答案就是能屈能伸两个虫子。成语拼音:néng qū néng shēn
    成语解释:屈:弯曲;伸:伸直。

    生活 2021年10月20日
  • 怎么理解MySQL的API接口

    技术怎么理解MySQL的API接口这篇文章给大家介绍怎么理解MySQL的API接口,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。MySQL 的 API 接口为了方便应用程序的开发,MySQL 提供了

    攻略 2021年11月17日
  • 基于RTVC-7 Voice Cloning Model 的Tuned-GE2E-SayEN-EarSpeech 搭建怎样的

    技术基于RTVC-7 Voice Cloning Model 的Tuned-GE2E-SayEN-EarSpeech 搭建怎样的基于RTVC-7 Voice Cloning Model 的Tuned-GE2E-SayEN

    攻略 2021年12月10日