jdk版本:1.8

数据结构:

HashMap的底层主要基于数组+链表/红黑树实现,数组优点就是查询块HashMap通过计算hash码获取到数组的下标来查询数据。同样也可以通过hash码得到数组下标,存放数据。

哈希表为了解决冲突,HashMap采用了链表法,添加的数据存放在链表中,如果发送冲突,将数据放入链表尾部。

上图左侧部分是一个哈希表,也称为哈希数组(hash table):

// table数组
transient Node<K,V>[] table;

table数组的引用类型是Node节点,数组中的每个元素都是单链表的头结点,链表主要为了解决上面说的hash冲突,Node节点包含:

  • hash hash值
  • key
  • value
  • next next指针

Node节点结构如下:

 static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next; Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
// 省略 get/set等方法
}

主要属性

// 储存元素数组
Node<K,V>[] table; // 元素个数
int size; // 数组扩容临界值,计算为:元素容量*装载因子
int threshold // 装载因子,默认0.75
float loadFactor; // 链表长度为 8 的时候会转为红黑树
int TREEIFY_THRESHOLD = 8; // 长度为 6 的时候会从红黑树转为链表
int UNTREEIFY_THRESHOLD = 6;
  • size记录元素个数
  • threshold 扩容的临界值,等于元素容量*装载因子
  • TREEIFY_THRESHOLD 8 链表个数增加到8会转成红黑树
  • UNTREEIFY_THRESHOLD 6 链表个数减少到6会退化成链表
  • loadFactor 装载因子,默认为0.75

loadFactor 装载因子等于扩容阈值/数组长度,表示元素被填满的程序,越高表示空间利用率越高,但是hash冲突的概率增加,链表越长,查询的效率降低。越低hash冲突减少了,数据查询效率更高。但是示空间利用率越低,很多空间没用又继续扩容。为了均衡查询时间使用空间,系统默认装载因子为0.75

获取哈希数组下标

添加、删除和查找方法,都需要先获取哈希数组的下标位置,首先通过hash算法算出hash值,然后再进行长度取模,就可以获取到元素的数组下标了。

首先是调用hash方法,计算出hash值

static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

先获取hashCode值,然后进行高位运算,高位运算后的数据,再进行取模运算的速度更快。

算出hash值之后,再进行取模运算:

(n - 1) & hash

上面的n是长度,计算的结果就是数组的下标了。

构造方法

HashMap()

     /**
* default initial capacity (16)
* the default load factor (0.75).
*/
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR;
}

设置默认装载因子0.75,默认容量16

HashMap(int initialCapacity)

// 指定初始值大小
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
} // 指定初始值和默认装载因子 0.75
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);
}

HashMap(int initialCapacity) 指定初始容量,调用HashMap(int initialCapacity, float loadFactor) 其中loadFactor为默认的0.75

首先做容量的校验,小于零报错,大于最大容量赋值最大值容量。然后做装载因子的校验,小于零或者是非数字就报错。

tableSizeFor使用右移和或运算,保证容量是2的幂次方,传入2的幂次方,返回传入的数据。传入不是2的幂次方数据,返回大于传入数据并接近2的幂次方数。比如:

  • 传入10返回16
  • 传入21返回32

HashMap(Map<? extends K, ? extends V> m)

public HashMap(Map<? extends K, ? extends V> m) {
this.loadFactor = DEFAULT_LOAD_FACTOR;
putMapEntries(m, false);
}

将集合m的数据添加到HashMap集合中,先设置默认装载因子,然后调用putMapEntries添加集合元素到HashMap中,putMapEntries是遍历数组,添加数据。

总结

本文基于jdk1.8解析HashMap源码,主要介绍了:

  • HashMap 是基于数组+链表/红黑树结构实现。采用链表法解决hash冲突。
  • Node 节点记录了数据的keyhashvalue以及next指针。
  • HashMap主要属性:
    • size 元素个数
    • table[] 哈希数组
    • threshold 扩容的阈值
    • loadFactor 装载因子
    • TREEIFY_THRESHOLD 8,链表个数为8转成红黑树。
    • UNTREEIFY_THRESHOLD 6 ,链表个数为6红黑树转为链表。
  • 添加、删除以及查找元素,首先要先获取数组下标,HashMap先调用hasCode方法,hashCode()的高16位异或低16位,大大的增加了运算速度。然后再对数组长度进行取模运算。本质就是取key的hashCode值、高位运算、取模运算
  • HashMap几个构造方法:
    • HashMap()设置默认装载因子0.75和默认容量16
    • HashMap(int initialCapacity)设置初始容量,默认装载因子0.75,容量是一定要是2的幂次方,如果不是2的幂次方,增加到接近2的幂次方数。
    • HashMap(Map<? extends K, ? extends V> m)主要是遍历添加的集合,添加数据。

参考

深入浅出HashMap的设计与优化

Java 8系列之重新认识HashMap

感觉不错的话,就点个赞吧!

详解HashMap源码解析(上)的更多相关文章

  1. 详解HashMap源码解析(下)

    上文详解HashMap源码解析(上)介绍了HashMap整体介绍了一下数据结构,主要属性字段,获取数组的索引下标,以及几个构造方法.本文重点讲解元素的添加.查找.扩容等主要方法. 添加元素 put(K ...

  2. SpringBoot之DispatcherServlet详解及源码解析

    在使用SpringBoot之后,我们表面上已经无法直接看到DispatcherServlet的使用了.本篇文章,带大家从最初DispatcherServlet的使用开始到SpringBoot源码中Di ...

  3. AFNetworking 3.0 使用详解 和 源码解析实现原理

    AFN原理&& AFN如何使用RunLoop来实现的: 让你介绍一下AFN源码的理解,首先要说说封装里面主要做了那些重要的事情,有那些重要的类(XY题) 一.AFN的实现步骤: NSS ...

  4. cas客户端流程详解(源码解析)--单点登录

    博主之前一直使用了cas客户端进行用户的单点登录操作,决定进行源码分析来看cas的整个流程,以便以后出现了问题还不知道是什么原因导致的 cas主要的形式就是通过过滤器的形式来实现的,来,贴上示例配置: ...

  5. CharacterEncodingFilter详解及源码解析

    字符编码过滤器  (Spring框架对字符编码的处理) 基于函数回调,对所有请求起作用,只在容器初始化时调用一次,依赖于servlet容器. web.xml配置文件 <filter> &l ...

  6. 六张图详解LinkedList 源码解析

    LinkedList 底层基于链表实现,增删不需要移动数据,所以效率很高.但是查询和修改数据的效率低,不能像数组那样根据下标快速的定位到数据,需要一个一个遍历数据. 基本结构 LinkedList 是 ...

  7. Ros学习——移动机器人Ros导航详解及源码解析

    1 执行过程 1.运行仿真机器人fake_turtlebot.launch:加载机器人模型——启动机器人仿真器——发布机器人状态 2.运行amcl节点fake_amcl.launch:加载地图节点ma ...

  8. 详解ConCurrentHashMap源码(jdk1.8)

    ConCurrentHashMap是一个支持高并发集合,常用的集合之一,在jdk1.8中ConCurrentHashMap的结构和操作和HashMap都很类似: 数据结构基于数组+链表/红黑树. ge ...

  9. Java SPI机制实战详解及源码分析

    背景介绍 提起SPI机制,可能很多人不太熟悉,它是由JDK直接提供的,全称为:Service Provider Interface.而在平时的使用过程中也很少遇到,但如果你阅读一些框架的源码时,会发现 ...

随机推荐

  1. 数据结构篇(2) ts实现单循环链表

    JS的class可以通过extends关键字实现类似其他语言的继承效果,比起使用一个extends关键字,在es5中实现继承要复杂一些,可以通过修改原型链的方法实现继承,让一个原型对象等于另一个类型的 ...

  2. 《Streaming Systems》第二章: 数据处理中的 What, Where, When, How

    本章中,我们将通过对 What,Where,When,How 这 4 个问题的回答,逐步揭开流处理过程的全貌. What:计算什么结果? 也就是我们进行数据处理的目的,答案是转换(transforma ...

  3. 超清晰的 DNS 原理入门指南,看这一篇就够了~

    点击上方"开源Linux",选择"设为星标" 回复"学习"获取独家整理的学习资料! DNS 是互联网核心协议之一.不管是上网浏览,还是编程开 ...

  4. JDBC:加载数据库驱动、连接数据库(详细讲解)

    加载数据库驱动: 1)由于Java是一个纯面向对象语言,任何事物在其中都必须抽象成类或者类对象,数据库也不例外,JDBC同样也把数据库抽象成面向对象的结构: 2)JDBC将整个数据库驱动器在底层抽象成 ...

  5. 深入了解tomcat中servlet的创建方式实现

    Tomcat如何创建Servlet? A.先到缓存中寻找有没有这个对象(Servlet是单实例的,只会创建一次) 如果没有: 1.通过反射去创建相应的对象(执行构造方法) 2.tomcat会把对象存放 ...

  6. mmsegmentation中构造自己的数据集和数据加载部分,跑现有demo

    在mmsegmentation中训练自己的数据集 先在mmse/dataset下创建一个python文件,我的名字是my_thermal_dataset.py 在其中填写下面内容 这里要注意,在设置s ...

  7. 一个登录点两个逻辑漏洞-edusrc

    最近呢, 也是基础漏洞学的差不多了, 就在edusrc上面实战, 刚开始搞一些信息泄漏啥的, 提交了十几个, 结果就他娘的通过了一个. 咱也就不碰信息泄漏了, 没得意思. 关于这个学校测试时也是有坑的 ...

  8. 提升站点SEO的7个建议

    1.使用HTTPS 谷歌曾发公告表示,使用安全加密协议(HTTPS),是搜索引擎排名的一项参考因素. 所以,在域名相同情况下,HTTPS站点比HTTP站点,能获得更好的排名. 在网络渠道分发或合作上, ...

  9. 【数据库】MySQL如何删除索引

    1.查看表上的索引 -- 查看table_name表上的索引 show index from table_name ; 2.删除表上的索引 删除索引可以使用ALTER TABLE或DROP INDEX ...

  10. 好客租房22-react组件的两种创建方式(类组件)

    2.2使用类创建组件 类组件:使用ES6的class创建的组件 约定1:类组件必须以大写字母开头 约定2:类组件应该继承react.component父类 从中可以使用父类的方法和属性 约定3:组件必 ...