hashMap是java最常用的Key-Value形式的集合。了解其原理和底层代码是很有必要的,今天就记录下对HashMap的.put()方法的研究分析(元素添加方法);

先说下个人研究分析结果:

  1. HashMap在实例初始化的时候并没有对存放元素的容器(1.8版本指数组链表红黑树、1.7版本指数组加链表)进行初始化,只是根据传参对相关属性进行了赋值。容器真正的初始化是在调用put()方法的时候实现的。初始化传参解释:initialCapacity 初始化容量和容器初始值有关,如果没有指定传参,则容器初始值默认值DEFAULT_INITIAL_CAPACITY=16,如果传参,则使用大于等于传参值的最小2的次方的,比如传值是5,那么容器初始化值便是2的3次方即8,另外传值大小有限制不能大于MAXIMUM_CAPACITY=1<<30,如果超过当前值则改变initialCapacity的值为MAXIMUM_CAPACITY的值 。loadFactor 加载因子和扩容有关,如果没指定传参,则使用默认值DEFAULT_LOAD_FACTOR=0.75f。
  2. 通过put()方法发现HashMap元素存放的数据结构是先生成数组,如果发生hash冲突则变成数组挂链表,默认当链表长度大于等于8且数组长度大于63的时候链表转为红黑树。验证了jdk1.8 HashMAp元素存放结构的说法是数组加链表+红黑树的说法。
  3. put()方法是有返回值的,返回值根据不同情况返回不同值,当添加元素的key在容器中不存在,那么元素返回为空,当添加元素的key在容器中存在则替换容器中的元素,并返回容器中原来的旧值元素

从new HashMap到调用put()方法分析底层源码验证上边结论:

       //实例化一个hashmap 在IDEA编辑器通过Ctrl+鼠标左键点击HashMap可以查看源码HashMap<String,String> s=new HashMap();//添加两个元素  他们key相同,value不同Object put = s.put("key键","第一个值" );Object put1 = s.put("key键","第二个值");System.out.println(put);System.out.println(put1);//输出结果put=null put1的key和value分别为"key键","第一个值"

分析HashMap构造函数相关源码:

 //有参构造,分别传入初始化容量值和加载因子*******1public 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);//根据传入的常量值设置容器初始大小}//有参构造传入初始化容量***********2     public HashMap(int initialCapacity) {this(initialCapacity, DEFAULT_LOAD_FACTOR);//内部调用了1构造,加载因子使用了默认值}
//空参构造***********3 所有值默认public HashMap() {this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted}
//有参构造传入另外一个map集合(暂不过多分析当前构造函数,其原理是一样的)public HashMap(Map<? extends K, ? extends V> m) {this.loadFactor = DEFAULT_LOAD_FACTOR;//加载因子使用默认值,初始化容量值(m.size()/loadFactor)+1.0f putMapEntries(m, false);//把另外一个map集合元素全部放到当前map}

通过上边源码我们可以发现在hashMap实例初始化的时候并没有初始化容器的大小,只是对相关属性进行了赋值。而且我们传入的初始化容量值并没有直接赋值给初始化容量相关属性threshold(容量阈值),而是调用一个叫tableSizeFor(int cap)的方法传入initialCapacity值进行处理之后返回一个值给threshold.查看该方法源码 我们就会发现其内部通过多次位移和按位或运算获得了大于等于传参initialCapacity的最小2的次方的值。

tableSizeFor(int cap)源码如下:

   /*** Returns a power of two size for the given target capacity(这是一个令人佩服的获取最小大于等于传参二次方数的算法).*/static final int tableSizeFor(int cap) {int n = cap - 1;//这里先减1是为了防止cap本身就是2的次方数,比如cap是4,那么进行下列位移运会得到错误的值8.这是不对的。因为4本身就是2的次方数n |= n >>> 1;//这段代码的意思是无符号向右位移一位然后再和原来再进行按位或操作得到新的n值。举例:假如原来n是3 在计算机二进制表示是0000 0011,向右移一位则变成0000 0001,这是两者按位或结果是0000 0011,换算成十进制便是3n |= n >>> 2;//下方同理n |= n >>> 4;//下方同理n |= n >>> 8;//下方同理n |= n >>> 16;//下方同理//利用三元运算符判断 当得到的n小于0则返回1 大于最大默认值,则返回最大值,否者返回n+1(就是最小大于等于cap的二次方数 也就是你初始化传的initialCapacity的值的最小大于等于的二次方数)return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;}

分析put()方法的源码:

点击进去put()方法的源码我们会发现其内部只调用putVal()方法和hash(key)方法,并发putVal()方法的返回值返回。二 hash(key)顾名思义仅仅对key做了hash操作,也就是说真正实现添加元素的方法其实是putVal()方法。

 public V put(K key, V value) {return putVal(hash(key), key, value, false, true);}

所以我们简单分析下hash(key)方法 重点分析putVal()方法


//当key为null 返回0 key如果不为空 那么key的hash方法返回的值便是 key的真正的hashcode按位异或(key的哈希值右移16位之后的值)这样做的好处就是 会让得到的下标更加散列 有助于减少hash冲突
static final int hash(Object key) {int h;return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);}
/*** Implements Map.put and related methods.** @param hash hash for key* @param key the key* @param value the value to put* @param onlyIfAbsent if true, don't change existing value* @param evict if false, the table is in creation mode.* @return previous value, or null if none*/final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {Node<K,V>[] tab; Node<K,V> p; int n, i;
//这里把table赋值给tab并判断是否为空,tab如果不为空,把tab的长度赋值给n,并判断.这里table指代容器.这里的目的是看是不是第一次调用put方法,如果是则调用调整容器大小的方法resize()完成容器的初始化(此时容器想当于是一维数组)并获取容器内元素长度赋值给n,resize()方法后边介绍。if ((tab = table) == null || (n = tab.length) == 0)n = (tab = resize()).length;
//获取当前tab数组的最大下标与当前传入元素hash(key)进行按位与操作判断tab第i为是否存在元素( 通过(n - 1) & hash进行与运算得到的就是新元素应该存放的位置),如果该位置没有元素则new一个节点传入当前元素,否则下一步if ((p = tab[i = (n - 1) & hash]) == null)tab[i] = newNode(hash, key, value, null);else {Node<K,V> e; K k;
//判断p元素也就是tab[i = (n - 1) & hash]元素的Hash值和当前插入元素hash值以及两者key是否一致,若一致则把p的值赋值给e 用于后期元素替换(解决key一样但是value不一样的情况)if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))e = p;
//用于处理已经生成红黑树的情况else if (p instanceof TreeNode)e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);else {
//如果p元素也就是tab[i = (n - 1) & hash]元素的Hash值和当前插入元素hash值以及两者key不一致,则生成链表形式。for (int binCount = 0; ; ++binCount) {if ((e = p.next) == null) {//p元素的下一个元素赋值给e,并判断是否为空p.next = newNode(hash, key, value, null);//如果空则直接把把当前插入元素插入节点即可if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st//这里是判断如果循环查找次数大于转红黑树阈值(8)开始尝试把链表转成红黑树(这个时候只是尝试,进入treeifyBin方法还要再判断数组长度是否超过64才会真正的转)treeifyBin(tab, hash);break;//跳出}
//这里是判断如果p.next不等于空时候e也就是p.next的hash值和当前插入元素hash值以及两者key是否一致,若一致,则跳出当前循环,后续处理eif (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))break;//跳出p = e;//把p指向p的下一个节点 }}
//这里便是用于当容器中存在key和待插入元素key hash值和值一样的时候的处理方式if (e != null) { // existing mapping for keyV oldValue = e.value;//把容器中旧值复制给oldValueif (!onlyIfAbsent || oldValue == null)//onlyIfAbsent是个标志 默认是false,一般不做变动e.value = value;//把当前元素值赋值给e.valueafterNodeAccess(e);//这段代码没看懂 在这里好像没有意义。方法体内是空的return oldValue; //put方法完成 返回旧值}}++modCount;//相当于版本校验 用于快速失败 集合类元素一般都有这个校验if (++size > threshold)//用于扩容 当当前容器内元素数量大于容量阈值thresholdresize();//调用调整大小方法容器大小afterNodeInsertion(evict);//这段代码没看懂 在这里好像没有意义。方法体内是空的return null;//新key时候返回的值}

分析了解JDK1.8版本的Java集合HashMap的put()方法相关推荐

  1. 深入java集合-HashMap

    本文为读书笔记,书籍为java并发编程的艺术 hashmap资料来自b站黑马 文章目录 1.HashMap 1.1 HashMap成员变量 问题: 为什么必须是2的n次幂?如果输入值不是2的幂比如10 ...

  2. java集合之TreeMap 构造器 方法 比较器

    java集合之TreeMap 基于红黑树(Red-Black tree)的 NavigableMap 实现. 映射根据其键的自然顺序进行排序,或者通过映射创建时提供的 Comparator 进行排序, ...

  3. Java集合HashMap

    HashMap Map接口的一个实现类 用于存储键值对映射关系 重复键 如果,出现重复键,将覆盖原有键的Value值 package bhz.aio;import java.util.HashMap; ...

  4. Java集合- HashMap 的底层数据结构实现原理

    一.HashMap 的数据结构 JDK1.8 之前 JDK1.8 之前 HashMap 底层是 数组和链表 结合在一起使用也就是 链表散列. HashMap 通过 key 的 hashCode 经过扰 ...

  5. Java重写HashMap的toString方法

    新建类myHashMap重写HashMap的toString方法,让HashMap按照我们想要的格式输出. import java.util.*;class myHashMap<K,T> ...

  6. JDK1.8版本,java并发框架支持锁包括

    1.自旋锁,自旋,jvm默认是10次,由jvm自己控制,for去争取锁 2.阻塞锁 被阻塞的线程,不会争夺锁 3.可重入锁,多次进入改锁的域 4.读写锁 5.互斥锁,锁本身就是互斥的 6.悲观锁,不相 ...

  7. Java集合——HashMap、HashTable以及ConCurrentHashMap异同比较

    转发:https://www.cnblogs.com/zx-bob-123/archive/2017/12/26/8118074.html 0. 前言 HashMap和HashTable的区别一种比较 ...

  8. Java集合—HashMap底层原理

    原文链接:最通俗易懂搞定HashMap的底层原理 HashMap的底层原理面试必考题.为什么面试官如此青睐这道题?HashMap里面涉及了很多的知识点,可以比较全面考察面试者的基本功,想要拿到一个好o ...

  9. java 集合初始化_6种方法初始化JAVA中的list集合

    List 是 Java 开发中经常会使用的集合,你们知道有哪些方式可以初始化一个 List 吗?这其中不缺乏一些坑,今天栈长我给大家一一普及一下. 1.常规方式 List languages = ne ...

最新文章

  1. 期末微积分考试试题求解 :利用python求解
  2. python 四种逐行读取文件内容的方法
  3. EXC_BAD_ACCESS调试
  4. POJ2112 Optimal Milking
  5. .net mysql 特殊字符转义字符_MySQL 特殊字符转义问题
  6. synchronized 王的后宫总管,线程是王妃
  7. win2003 iis上运行asp.net配置
  8. Threejs实现3d地球记录(1)
  9. 用python做网站的步骤_Python建网站的步骤
  10. 【ESP32之旅】ESP32C3 Arduino库使用方法
  11. HPUX 无法启动 卡在EVN_MC_INITIATED
  12. C++程序设计课程主页-2015级
  13. 应用ArcGIS和COORD软件进行坐标七参数转换的方法
  14. AccountManager getAccount 在Android O 8.0版本中获取为 null ?
  15. 【EI会议合集 | 高校联办】机器学习、通信与智能技术等多领域,可推优发表SCI...
  16. 漫画:细思极恐,生男女几率相同,那为什么很多国家男女比例还失衡呢?
  17. 【步兵 c++】教科书般的A*寻路算法
  18. CUBEMX移植RTTHREAD步骤
  19. Sample Codes之Query features from a FeatureLayer
  20. EFI PXE 0 for IPv4解决方法,Boot device……解决方法,Windows无法完成安装解决方法

热门文章

  1. 页面生成器实现及源码下载
  2. 目标检测tricks总结(记录)
  3. R语言实战-如何分析QQ群记录5-词云
  4. 技巧分享:如何给视频加水印?
  5. 阿里云禁用25端口,如何实现邮件的发送?
  6. 怎么查看邮件服务器25端口,怎么样用telnet命令工具测试发邮件的25端口是否畅通...
  7. 济南双软认证认定条件
  8. 一站式地图服务平台“地图易”——平台架构解析
  9. 杰理AC690X系列---开机默认进入上一次关机前的模式(15)
  10. aspose-word for java word转pdf 解决遇到的问题