Java8 HashMap扩容算法实例解析

这篇文章主要介绍了Java8 HashMap扩容算法实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下

Java8的HashMap扩容过程主要就是集中在resize()方法中

 final Node<K,V>[] resize() {
   // ...省略不重要的
 }

其中,当HashMap扩容完毕之后,需要对原有的数据进行转移。因为容量变大了,部分元素的位置因此要变更,因而出现了下面的这个转移过程。

转移过程大致是:依次从旧数组里取值,然后从该值对应的链表上依次取出节点,对节点取模分别放入lo链表和hi链表,当链表中节点遍历完后,分别把lo链表和hi链表放入新数组的不同位置。

在看到如下第15行时,我在想,为什么(e.hash & oldCap)== 0时就放入lo链表,否则就是hi链表?

说到这个问题,那我们就要回顾下HashMap存入新元素的过程了。看下面的第45行,可以发现插入时是使用(n - 1) & hash来计算位置的,即数组长度-1,而扩容移位是使用数组长度n计算的,那这是为什么呢?

for (int j = 0; j < oldCap; ++j) {
  Node<K,V> 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<K,V>)e).split(this, newTab, j, oldCap);
    else { // preserve order
      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;
      }
      if (hiTail != null) {
        hiTail.next = null;
        newTab[j + oldCap] = hiHead;
      }
    }
  }
}

final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
  // ...省略不重要的
  if ((p = tab[i = (n - 1) & hash]) == null)
    tab[i] = newNode(hash, key, value, null);
  else {
  // ...省略不重要的
}

像我们看Java8的HashMap源码,应该都应该知道HashMap的底层数组长度都是2的n方的值

那么我们就假设一个底层数组长度为8的HashMap模拟进行插入元素和扩容移位的过程

长度n=8 ----> 0x1000

n-1   ----> 0x0111

此时写入两个元素,两个元素的hash值分别为hash1 = 0x0101,hash2 = 0x1101

hash1 & n-1 = 0x0101

hash2 & n-1 = 0x0101

两个hash取模后的结果是一致的,所以它们会在同一个地方组成链表

那么此时如果要进行扩容移位呢?

hash1 & n = 0x0000

hash2 & n = 0x1000

此时两者的结果是不一样的,并且相差0x1000即10进制的8即数组长度.。

所以这也就是为什么上图15行只判断==0的原因,因为这个取模结果只有0和1两种值(数组长度是2的n次方,只有除了符号位外的最高位为1)

而两个取模结果等于数组长度,这也就是为什么上图第32和36行那么处理的原因。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Java中遍历ConcurrentHashMap的四种方式详解

    这篇文章主要介绍了Java中遍历ConcurrentHashMap的四种方式详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 方式一:在for-each循环中使用entries来遍历 System.out.println("方式一:在for-each循环中使用entries来遍历");for (Map.Entry<String, String> entry: map.entrySet()) { System.out.pr

  • java在hashmap初始化时赋初值过程解析

    Java中的HashMap是一种常用的数据结构,一般用来做数据字典或者Hash查找的容器. 一般我们初始化并赋初值是这样做的: HashMap<String, Object> map = new HashMap<>(); map.put("name", "yanggb"); map.put("lover", "huangq"); 但是有时候我们会想在一个表达式中完成初始化并赋初值的操作: HashMap

  • Java源码解析HashMap的tableSizeFor函数

    aka,HashMap的容量大小必须为2的指数,即16,32,64,128这样的值.那么,在构造函数中,如果调用者指定了HashMap的初始大小不是2的指数,那么,HashMap的tableSizeFor函数,会计算一个大于或等于给定参数的2的指数的值.先来看一下tableSizeFor函数的源码,如下 /** * Returns a power of two size for the given target capacity. **/ static final int tableSizeFo

  • Java源码解析ConcurrentHashMap的初始化

    首先看一下代码 private final Node<K,V>[] initTable() { Node<K,V>[] tab; int sc; while ((tab = table) == null || tab.length == 0) { // 第一次检查 if ((sc = sizeCtl) < 0) Thread.yield(); // lost initialization race; just spin else if (U.compareAndSwapInt

  • Java中Arraylist动态扩容方法详解

    前言 本文主要给大家介绍了关于Java中Arraylist动态扩容的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧. ArrayList 概述 ArrayList是基于数组实现的,是一个动态数组,其容量能自动增长.ArrayList不是线程安全的,只能用在单线程环境下.实现了Serializable接口,因此它支持序列化,能够通过序列化传输:实现了RandomAccess接口,支持快速随机访问,实际上就是通过下标序号进行快速访问:实现了Cloneable接口,能被克隆.

  • Java HashMap 如何正确遍历并删除元素的方法小结

    (一)HashMap的遍历 HashMap的遍历主要有两种方式: 第一种采用的是foreach模式,适用于不需要修改HashMap内元素的遍历,只需要获取元素的键/值的情况. HashMap<K, V> myHashMap; for (Map.entry<K, V> item : myHashMap.entrySet()){ K key = item.getKey(); V val = item.getValue(); //todo with key and val //WARNI

  • Java源码解析HashMap的keySet()方法

    HashMap的keySet()方法比较简单,作用是获取HashMap中的key的集合.虽然这个方法十分简单,似乎没有什么可供分析的,但真正看了源码,发现自己还是有很多不懂的地方.下面是keySet的代码. public Set<K> keySet() { Set<K> ks = keySet; if (ks == null) { ks = new KeySet(); keySet = ks; } return ks; } 从代码中了解到,第一次调用keySet方法时,keySet

  • java HashMap扩容详解及实例代码

    HashMap扩容 前言: HashMap的size大于等于(容量*加载因子)的时候,会触发扩容的操作,这个是个代价不小的操作. 为什么要扩容呢?HashMap默认的容量是16,随着元素不断添加到HashMap里,出现hash冲突的机率就更高,那每个桶对应的链表就会更长, 这样会影响查询的性能,因为每次都需要遍历链表,比较对象是否相等,一直到找到元素为止. 为了提升查询性能,只能扩容,减少hash冲突,让元素的key尽量均匀的分布. 扩容基本点 加载因子默认值是0.75 static final

  • Java数组的扩容代码示例

    在写程序的过程中,我们常常会碰见数组空间不够用的情况,比如我已经初始化了一个数组int []a = {1,2,3,4,5,6,7,8,9,10} ;这时,我想往数组下标3的位置插入一个元素,该怎么做?用C语言实现太难了吧,需要调用memcpy函数要一个一个偏,但是在java中就不用那么麻烦了,有种叫数组的扩容方式,轻松实现.来看看代码: public class HelloWorld { public static void main(String[] args){ // Scanner s =

  • Java8 HashMap扩容算法实例解析

    这篇文章主要介绍了Java8 HashMap扩容算法实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 Java8的HashMap扩容过程主要就是集中在resize()方法中 final Node<K,V>[] resize() { // ...省略不重要的 } 其中,当HashMap扩容完毕之后,需要对原有的数据进行转移.因为容量变大了,部分元素的位置因此要变更,因而出现了下面的这个转移过程. 转移过程大致是:依次从旧数组里取值,然后从

  • Java HashMap原理及实例解析

    这篇文章主要介绍了Java HashMap原理及实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 示例 1 : HashMap的键值对 HashMap储存数据的方式是-- 键值对 package collection; import java.util.HashMap; public class TestCollection { public static void main(String[] args) { HashMap<String

  • Java8 Stream中间操作实例解析

    这篇文章主要介绍了Java8 Stream中间操作实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 介绍Stream Stream 使用一种类似用于SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象. Stream API可以极大提高Java程序员的生产力,让程序员写出高效率.干净.简洁的代码. 这种风格将要处理的元素集合看作一种流,流在管道中传输,并且可以在管道的节点上进行处理,比如筛选,排序,聚合等

  • PHP实现克鲁斯卡尔算法实例解析

    本文实例展示了PHP实现的格鲁斯卡尔算法(kruscal)的实现方法,分享给大家供大家参考.相信对于大家的PHP程序设计有一定的借鉴价值. 具体代码如下: <?php require 'edge.php'; $a = array( 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i' ); $b = array( 'ab' => '10', 'af' => '11', 'gb' => '16', 'fg' => '17', 'bc' =>

  • 通过实例解析java8中的parallelStream

    这篇文章主要介绍了通过实例解析java8中的parallelStream,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 about Stream 什么是流? Stream是java8中新增加的一个特性,被java猿统称为流. Stream 不是集合元素,它不是数据结构并不保存数据,它是有关算法和计算的,它更像一个高级版本的 Iterator.原始版本的 Iterator,用户只能显式地一个一个遍历元素并对其执行某些操作:高级版本的 Stream

  • LRU算法及Apache LRUMap源码实例解析

    目录 1. 什么是LRU 1.1 自定义实现LRU的要求 1.2 Apache LRUMap示例 1.2.1 pom依赖 1.2.2 demo 2. 源码解析 2.1 设计 2.2 数据结构 2.3 方法解析put get remove 2.3.1 get方法 2.3.2 remove方法 2.3.3 put方法 3. 总结 1. 什么是LRU LRU(least recently used) : 最近最少使用 LRU就是一种经典的算法,在容器中,对元素定义一个最后使用时间,当新的元素写入的时候

  • Java8新特性Stream短路终端操作实例解析

    这篇文章主要介绍了Java8新特性Stream短路终端操作实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 传入一个谓词,返回传为boolean,如果符合条件,则直接结束流. 匹配所有 allMatch 任意匹配 anymMatch 不匹配 noneMatch 查找首个 findFirst 查找任意 findAny 匹配所有 allMatch /匹配所有 allMatch @Test public void allMatchTest()

  • JAVA8 STREAM COLLECT GROUPBY分组实例解析

    这篇文章主要介绍了JAVA8 STREAM COLLECT GROUPBY分组实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 实体类People,有个返回list的buildPeopleList方法,方便测试. import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; impo

  • python有序查找算法 二分法实例解析

    这篇文章主要介绍了python有序查找算法 二分法实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 二分法是一种快速查找的方法,时间复杂度低,逻辑简单易懂,总的来说就是不断的除以2除以2... 但是需要注意: 待查找的序列区间单调有序 例如需要查找有序数组arr里面的某个关键字key的位置,那么首先确认arr的中位数或者中点center,下面分为三种情况: 假如arr[center]>key,说明key在arr中心左边范围: 假如arr[

随机推荐