HashMap继承了AbstractMap这个抽象类 并且实现了Map这个接口,可以实现clone和序列化


底层数据结构 : 数组 + 单链表 + 红黑树

【说明】 每一个数组+ 单链表/红黑树 叫做桶 也叫做段



定义了hash表所对应的数组的长度

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16   

hash表中数组的最大长度

static final int MAXIMUM_CAPACITY = 1 << 30;  

扩充因子,数组里存放的值达到数组长度的75%,就开始扩容

static final float DEFAULT_LOAD_FACTOR = 0.75f; 

单链表的长度,如果超过8,就把单链表转为红黑树

final int TREEIFY_THRESHOLD = 8; 

桶中红黑树立元素个数,小等于6时,转为单链表

static final int UNTREEIFY_THRESHOLD = 6; 

1、hash表中数组的作用

存放点链表或树结构的首地址(不存具体数据)


2、链表转红黑树的条件为什么是8?

HashMap在JDK1.8及以后的版本中引⼊了红⿊树结构,若桶中链表元素个数⼤于等于8时,链表转换成树结构;若桶中链表元素个数⼩于等于6时,树结构还原成链表。因为红⿊树的平均查找⻓度是log(n),⻓度为8的时候,平均查找⻓度为3,如果继续使链表,平均查找⻓度为8/2=4,这才有转换为树的必要。链表⻓度如果是⼩于等于6,6/2=3,虽然速度也很快的,但是转化为树结构和⽣成树的时间并不会太短。还有选择6和8,中间有个差值7可以有效防⽌链表和树频繁转换。假设⼀下,如果设计成链表个数超过8则链表转换成树结构,链表个数⼩于8则树结构转换成链表,如果⼀个HashMap不停的插⼊、删除元素,链表个数在8左右徘徊,就会频繁的发⽣树转链表、链表转树,效率会很低。

也就是说如果一开始为6此时是链表 那么再插入一个元素变为7的时候依旧是链表 再增加一个 变为8的时候才转为红黑树

但是如果 一开始为8此时为红黑树 那么删除一个元素变7的时候依旧是红黑树 再删除一个 变为6的时候才转换为链表


3、HashMap如何存储key-value的形式

因为在HashMap源码中 有一个静态内部类Node<K,V> ,当每存入一个值的时候就会创建也就是new出一个Node节点(对象),该对象中有K属性和V属性,分别来存储Key和Value


4、HashMap存放数据的特点

无序、key值唯一,如果Key重复,value值会进行覆盖

在存值的时候,首先对Key进行hash值计算,计算的结果就是hash表中数组的存放的位置


5、Hash值的算法

hashMap的key允许存null值,具体来说 hashMap的key和value都可以存null值

先定义变量h用来接收hashCode值

key为null时

当key为null的时候就将元素存储在数组中0这个位置

key不为null时

首先计算出key的hashcode值

将hashcode的值 无符号 向右移16位做异或运算,此时得到hash值

再将hash值与数组长度-1去做与运算

此时得出来的这个结果是一个0~数组长度中间的一个数,这个数就是数组中的索引,也就是地址在数组中存放的位置

【异或运算】相同为0不同为1

【与运算】 0&0=0;0&1=0;1&0=0;1&1=1


6、Hash值存放元素的方式

    public V put(K key, V value) {return putVal(hash(key), key, value, false, true);}
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {Node<K,V>[] tab; Node<K,V> 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<K,V> 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<K,V>)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 1sttreeifyBin(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 keyV oldValue = e.value;if (!onlyIfAbsent || oldValue == null)e.value = value;afterNodeAccess(e);return oldValue;}}++modCount;if (++size > threshold)resize();afterNodeInsertion(evict);return null;}

1、key调用hash算法计算出存储值在数组中的存放位置

2、判断当前的存储位置是否有元素

如果当前数组的存放位置为null 证明没有存放元素 那么就new Node(key,value) 将new Node之后的地址存放在数组的元素里 将key       和 value的值存放在链表中

如果当前位置有元素(hash值发生冲突 不同key经过hash计算结果可能是一样的),就顺着单链表,从首元素开始,逐个使用equals比       较key值是否相等

 如果key的equals值都不相等,那么new Node节点,用来存放key和value的值,把这个节点所对应的地址放在单链表的末尾,               存放后,判断单链表的长度是否大于 8,如果是,把单链表转为红黑树,不是的话依旧是链表

如果通过equals比较完,当前key和某个链表中Node的key相等(hash冲突),则使用当前的value去覆盖掉原有的value

3、存放完毕后,判断阈值:阈值=数组长度 * 0.75f 如果hashMap中元素的个数已经超过阈值,数组则进行扩容,每次       数组的长度扩容到原来的2倍


7、为什么数组长度要扩容2倍

扩容2倍后,计算出的hash值锁产生的hash冲突的几率最小。

扩容后,由于数组长度发生了改变,所有元素都要重新计算hash值(与数组长度-1做与运算),存放位置可能会发生改变

如果存放元素超过12个,最后new HashMap的时候指定数组的长度  (用存放的个数除以12)

因为扩容所有元素都要重新计算hash值所以我们应该尽量减少数组的扩容,根据存放的元素个数在最开始的时候就定义好数组的长度,具体执行的方法如下

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

8、HashMap和HashTable的区别

1. Hashtable是线程安全,而HashMap则非线程安全,Hashtable的实现方法里面大部分都添加了synchronized关键字来确保线程同步,因此相对而言HashMap性能会高一些,在多线程环境下若使用HashMap需要使用Collections.synchronizedMap()方法来获取一个线程安全的集合。

2. HashMap的键和值都可以为null,而Hashtable的键值都不能为null。

3. HashMap的初始容量为16,Hashtable初始容量为11,两者的填充因子默认都是0.75。HashMap扩展容量是当前容量翻倍即:capacity*2,Hashtable扩展容量是容量翻倍+1即:capacity*2+1(关于扩容和填充因子后面会讲)

4. 两者的哈希算法不同,HashMap是先对key(键)求hashCode码,然后再把这个码值得高位和低位做异或运算,源码如下:

HashMap: 得到key值得hashcode,

​      对hashcode值异或运算

​      对异或计算的结果  和  初始容量(数组大小)-1做&运算

static final int hash(Object key) {

​    int h;

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

}

i = (n - 1) & hash

然后把hash(key)返回的哈希值与HashMap的初始容量(也叫初始数组的长度)减一做&(与运算)就可以计算出此键值对应该保存到数组的那个位置上(hash&(n-1))。这里为什么不用key本身的hashcode方法,而又是右移动16位又是异或操作。开发人员这样做的目的是什么呢?是当数组容量很小的时候,计算元素在数组中的位置(n-1)&hash,只用到了hash值的低位,这样当不同的hash值低位相同,高位不同的时候会产生冲突。实际上的hash值将hashcode低16位与高16位做异或运算,相当于混合了高位和低位,增加了随机性。当然是冲突越少越好,元素的分布越随机越好。

而Hashtable计算位置的方式如下:

int hash = key.hashCode();int index = (hash & 0x7FFFFFFF) % tab.length;

直接计算key的哈希码,然后与2的31次方做&(与运算),然后对数组长度取余数计算位置。

HashMap底层源码解析相关推荐

  1. HashMap 底层源码细致分析

    JDK集合HashMap 底层源码细致分析 前言 提示:对于初始 HashMap 的小伙伴来说,不推荐直接硬啃,建议先看一下如下几个视频教程之后再回头好好理解.(一遍看不懂则反复看,一小块一小块的找对 ...

  2. HashSet源码解析(最好先看HashMap的源码解析)

    HashMap的源码解析:https://mp.csdn.net/console/editor/html/106188425 HashSet:Java中的一个集合类,该容器不允许包含重复的数值 pub ...

  3. hashmap的特性?HashMap底层源码,数据结构?Hashmap和hashtable ConcurrentHashMap区别?

    1.hashmap的特性? 允许空键和空值(但空键只有一个,且放在第一位) 元素是无序的,而且顺序会不定时改变 key 用 Set 存放,所以想做到 key 不允许重复,key 对应的类需要重写 ha ...

  4. JavaSE09:String、StringBuffer、StringBuilder底层源码解析(纯干货)

    写在前面 结束了多线程的学习后,常用类的学习立马就安排上了.在学习的过程中,发现源码是很重要的,只知道怎么用却不知道底层是很不好的,而我又太懒.看源码也是零零散散,所以本篇博客旨在鞭策自己多多阅读现阶 ...

  5. HashMap 底层源码详解(jdk1.8)

    目录 HashMap概述 Map家族 哈希表 哈希表扩容 构造方法 put()方法(第一次插入) resize()方法 让数组容量为2次幂的原因 get()方法 get()方法实现原理 put()方法 ...

  6. Hashmap底层源码

    仅个人理解如有不对,欢迎指正,初学者 以下内容都是源码粘贴,有些顺序混乱,自己查看hashmap源码对照着看,排版混乱见谅 hashset底层是一个hashmap  private static fi ...

  7. hashmap底层源码详解

    这里聊一下HashMap: HashMap底层数据结构: HashMap1.7之前数据结构是数组+链表 HashMap1.8之后数据结构加了红黑树(是用来处理hash冲突的) HashMap1.7之前 ...

  8. 《Spring》第二十一篇 事务底层源码解析

    目录 一.事务的底层执行原理 1. 解析注解@EnableTransactionManagement 2. Bean生命周期的初始化后阶段,校验是否需要进行AOP 3. 示例FruitsService ...

  9. spring源码分析04-spring循环依赖底层源码解析

    1. 什么是循环依赖 很简单,就是A对象依赖了B对象,B对象依赖了A对象. // A依赖了B class A{public B b; }// B依赖了A class B{public A a; } 如 ...

最新文章

  1. UA OPTI501 电磁波5 电磁场的基本物理量:电磁场的源与电磁场的强度
  2. 监听门后德美恢复网监合作
  3. 数组作为方法参数_传递地址
  4. 在Asp.net网页中使用接口
  5. LeetCode 1805. 字符串中不同整数的数目(哈希set)
  6. [人生]不经历风雨怎么见彩虹
  7. 1614. 括号的最大嵌套深度
  8. TOGAF认证自学宝典V2.0
  9. x10ti怎么禁用核显_笔电多显卡切换解决方案浅谈(ver2.2)
  10. lomboz连接mysql数据库_『在线等』 lomboz-eclipse连接mysql,连接失败
  11. 内外网双网卡同时上网
  12. 写得不错的一篇面试博文!
  13. 入门小白不到三个月就学会了用maya软件如何制作动画
  14. Go语言安装与环境配置(基于Windows)
  15. SecureCRT通过vim打开文件时显示行号
  16. 夜神模拟器报错 daemon still not running error: cannot connect to daemon
  17. grub4dos引导启动linux,Grub4Dos 手动引导指令
  18. ps如何做出动态火焰燃烧效果
  19. 基于gensim实现word2vec模型(附案例实战)
  20. gt2e鸿蒙系统,华为Watch GT2e评测:轻松开启年轻人一手掌控的百变运动潮酷生活...

热门文章

  1. Java生鲜电商平台-订单架构实战
  2. AutoLeaders控制组——51单片机学习笔记(DS18B20温度传感器、LCD1602、直流电机+PWM)
  3. Python项目:基于Python+Django实现药品管理系统
  4. H264 raw stream获取nalu的长度信息
  5. Matlab矩阵和数组的操作
  6. 遍历Python字典
  7. Git最佳实践(init、config、status、add、commit、diff、push) 1.0v
  8. 人工智能的逆向工程--反向智能研究综述
  9. 计算机关闭远程桌面,windows 远程桌面关闭 运行程序退出
  10. ARMv7 Processor modes