iis服务器助手广告广告
返回顶部
首页 > 资讯 > 精选 >如何进行HashMap扩容机制源码分析
  • 802
分享到

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

2023-06-02 13:06:03 802人浏览 安东尼
摘要

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

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

具体看源码之前,我们先简单的说一下HashMap的底层数据结构

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

  2、我们需要先了解一下HashMap底层的两个变量

  2-1:loadFactor: 加载因子,默认是0.75,这个值是经过反复测试最合适的值。

  2-2:threshold: 当map里面的数据大于这个threshold就会进行扩容

  注:详细了解loadFactor点击这里

  2、现在来看一下HashMap的构造方法

  hashMap的构造方法有4个。

  1、空参构造方法,这个时候加载因子为默认的0.75,并且不会创建空间。

  threshold 为0

  数组为null

  public HashMap() {

  this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted

  }

  2、给定初始化容量大小,这个构造方法里面会直接去调用第三个构造方法

  threshold 已经有值了

  数组为null

  public HashMap(int initialCapacity) {

  this(initialCapacity, DEFAULT_LOAD_FACTOR);

  }

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

  1、其实并不建议修改默认的加载因子。当然除非你很了解这里面的逻辑找到一个适合自己这个项目的加载因子

  2、先是判断你给的初始化容量是否合法,如果合法的话就用这个初始化容量计算出 threshold

  threshold 已经有值了

  数组为null

  public HashMap(int initialCapacity, float loadFactor) {

  if (initialCapacity < 0)

  throw new IllegalArgumentException("Illegal initial capacity: " +

  initialCapacity);

  if (initialCapacity > MAXIMUM_CAPACITY)

  initialCapacity = MAXIMUM_CAPACITY;

  if (loadFactor <= 0 || Float.isNaN(loadFactor))

  throw new IllegalArgumentException("Illegal load factor: " + loadFactor);

  this.loadFactor = loadFactor;

  this.threshold = tableSizeFor(initialCapacity);

  }

  4、把一个Map作为参数传递过来,加载因子适应默认的0.75。把其它Map转化成HashMap

  threshold 已经有值了

  数组为也不为空了

  public HashMap(Map m) {

  this.loadFactor = DEFAULT_LOAD_FACTOR;

  putMapEntries(m, false);

  }

  final void putMapEntries(Map m, boolean evict) {

  int s = m.size();

  if (s > 0) {

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

  float ft = ((float)s / loadFactor) + 1.0F;

  int t = ((ft < (float)MAXIMUM_CAPACITY) ?

  (int)ft : MAXIMUM_CAPACITY);

  if (t > threshold)

  threshold = tableSizeFor(t);

  }

  else if (s > threshold)

  resize();

  for (Map.Entry e : m.entrySet()) {

  K key = e.geTKEy();

  V value = e.getValue();

  putVal(hash(key), key, value, false, evict);

  }

  }

  }

  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扩容机制源码分析了,如果刚好有类似的疑惑,不妨参照上述分析进行理解。如果想知道更多相关知识,欢迎关注编程网精选频道。

--结束END--

本文标题: 如何进行HashMap扩容机制源码分析

本文链接: https://www.lsjlt.com/news/230170.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • 如何进行HashMap扩容机制源码分析
    这期内容当中小编将会给大家带来有关如何进行HashMap扩容机制源码分析,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。具体看源码之前,我们先简单的说一下HashMap的底层数据结构  1、HashMap底...
    99+
    2023-06-02
  • RocketMQ producer容错机制源码分析
    这篇文章主要介绍“RocketMQ producer容错机制源码分析”,在日常操作中,相信很多人在RocketMQ producer容错机制源码分析问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家...
    99+
    2023-07-05
  • 如何用源码分析Java HashMap实例
    本篇文章为大家展示了如何用源码分析Java HashMap实例,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。引言HashMap在键值对存储中被经常使用,那么它到底是如何实现键值存储的呢?一 Entr...
    99+
    2023-06-17
  • 如何进行FileZilla的源代码分析
    这篇文章将为大家详细讲解有关如何进行FileZilla的源代码分析,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。FileZilla是一种快速、可信赖的FTP客户端以及服务器端开放源代码程式,...
    99+
    2023-06-16
  • 如何进行ProxmoxVE虚拟机LVM扩容
    本篇文章为大家展示了如何进行ProxmoxVE虚拟机LVM扩容,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。-- Proxmox VE虚拟机lvm扩容(/dev/mapper/centos-root...
    99+
    2023-06-05
  • 不容错过的HashMap实现原理及源码分析
    哈希表(hash table)也叫散列表,是一种非常重要的数据结构,应用场景及其丰富,许多缓存技术(比如memcached)的核心其实就是在内存中维护一张大的哈希表,而HashMap的实现原理也常常出现在各类的面试题中,重要性可见一斑。本文...
    99+
    2023-06-02
  • JavaArrayList扩容机制原理深入分析
    目录扩容机制扩容原理源码分析扩容机制 ArrayList是一个底层基于数组实现的集合容器。当我们在创建ArrayList对象时,默认数组长度为10,当然也可以在创建时指定长度。之后在...
    99+
    2023-02-22
    Java ArrayList扩容机制 Java ArrayList Java扩容机制
  • 如何进行Netlink源码及实例的分析
    本篇文章给大家分享的是有关如何进行Netlink源码及实例的分析,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。前言这几天在看 ipvs 相关代码的时候又遇到了 netlink ...
    99+
    2023-06-15
  • RocketMQ producer容错机制源码解析
    目录1. 前言2. 失败重试3. 延迟故障3.1 最普通的选择策略3.2 延迟故障的实现1. 前言 本文主要是介绍一下RocketMQ消息生产者在发送消息的时候发送失败的问题处理?...
    99+
    2023-03-19
    RocketMQ producer容错机制 RocketMQ producer
  • 怎么进行ActionInvoker源码分析
    本篇内容介绍了“怎么进行ActionInvoker源码分析”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!throw new&nbs...
    99+
    2023-06-17
  • 如何进行jQuery源码的整体框架分析
    这篇文章将为大家详细讲解有关如何进行jQuery源码的整体框架分析,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。先附上jQuery的代码结构。JS代码(fu...
    99+
    2024-04-02
  • 如何进行jQuery EasyUI 1.2.6源码合集的分析
    如何进行jQuery EasyUI 1.2.6源码合集的分析,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。相信关注过jQuer...
    99+
    2024-04-02
  • 如何进行Redux的源码解析
    如何进行Redux的源码解析,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。预热redux 函数内部包含了大量柯里化函数以及代码...
    99+
    2024-04-02
  • 怎么进行FileZilla源代码分析
    怎么进行FileZilla源代码分析,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。FileZilla是一种快速、可信赖的FTP客户端以及服务器端开放源代码程式,具有多种特色...
    99+
    2023-06-16
  • 如何进行数据库中间件 MyCAT 源码分析
    这篇文章将为大家详细讲解有关如何进行数据库中间件 MyCAT 源码分析,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。1. 概述可能你在看到这个标题会小小的吃...
    99+
    2024-04-02
  • 如何进行Nginx内核优化的源代码分析
    如何进行Nginx内核优化的源代码分析,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。Nginx内核优化在不断的使用中有很多的问...
    99+
    2024-04-02
  • Java异步编程中如何进行FutureTask源码分析
    本篇文章给大家分享的是有关Java异步编程中如何进行FutureTask源码分析,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。Java的异步编程是一项非常常用的多线程技术。但之...
    99+
    2023-06-19
  • python列表中扩容机制的示例分析
    这篇文章将为大家详细讲解有关python列表中扩容机制的示例分析,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。1、说明在对列表进行添加数据项时,如果列表内部的容量已满则会触发扩容机制。2、判定当前列表是否...
    99+
    2023-06-15
  • Vue3响应式机制源码分析
    本篇内容介绍了“Vue3响应式机制源码分析”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!什么是响应式响应式一直都是 Vue 的特色功能之一;...
    99+
    2023-07-06
  • SpringCloud @RefreshScope刷新机制源码分析
    今天小编给大家分享一下SpringCloud @RefreshScope刷新机制源码分析的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我...
    99+
    2023-07-05
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作