详解HashMap源码解析(上)
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 节点记录了数据的
key
、hash
、value
以及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源码解析(上)的更多相关文章
- 详解HashMap源码解析(下)
上文详解HashMap源码解析(上)介绍了HashMap整体介绍了一下数据结构,主要属性字段,获取数组的索引下标,以及几个构造方法.本文重点讲解元素的添加.查找.扩容等主要方法. 添加元素 put(K ...
- SpringBoot之DispatcherServlet详解及源码解析
在使用SpringBoot之后,我们表面上已经无法直接看到DispatcherServlet的使用了.本篇文章,带大家从最初DispatcherServlet的使用开始到SpringBoot源码中Di ...
- AFNetworking 3.0 使用详解 和 源码解析实现原理
AFN原理&& AFN如何使用RunLoop来实现的: 让你介绍一下AFN源码的理解,首先要说说封装里面主要做了那些重要的事情,有那些重要的类(XY题) 一.AFN的实现步骤: NSS ...
- cas客户端流程详解(源码解析)--单点登录
博主之前一直使用了cas客户端进行用户的单点登录操作,决定进行源码分析来看cas的整个流程,以便以后出现了问题还不知道是什么原因导致的 cas主要的形式就是通过过滤器的形式来实现的,来,贴上示例配置: ...
- CharacterEncodingFilter详解及源码解析
字符编码过滤器 (Spring框架对字符编码的处理) 基于函数回调,对所有请求起作用,只在容器初始化时调用一次,依赖于servlet容器. web.xml配置文件 <filter> &l ...
- 六张图详解LinkedList 源码解析
LinkedList 底层基于链表实现,增删不需要移动数据,所以效率很高.但是查询和修改数据的效率低,不能像数组那样根据下标快速的定位到数据,需要一个一个遍历数据. 基本结构 LinkedList 是 ...
- Ros学习——移动机器人Ros导航详解及源码解析
1 执行过程 1.运行仿真机器人fake_turtlebot.launch:加载机器人模型——启动机器人仿真器——发布机器人状态 2.运行amcl节点fake_amcl.launch:加载地图节点ma ...
- 详解ConCurrentHashMap源码(jdk1.8)
ConCurrentHashMap是一个支持高并发集合,常用的集合之一,在jdk1.8中ConCurrentHashMap的结构和操作和HashMap都很类似: 数据结构基于数组+链表/红黑树. ge ...
- Java SPI机制实战详解及源码分析
背景介绍 提起SPI机制,可能很多人不太熟悉,它是由JDK直接提供的,全称为:Service Provider Interface.而在平时的使用过程中也很少遇到,但如果你阅读一些框架的源码时,会发现 ...
随机推荐
- 控制Python浮点数输出位数
技术背景 在Python的一些长效任务中,不可避免的需要向文本文件.二进制文件或者数据库中写入一些数据,或者是在屏幕上输出一些文本,此时如何控制输出数据的长度是需要我们注意的一个问题.比如对于一个二进 ...
- git 在 pull 或者合并分支的时候会遇到下图这个界面
可以不管(直接进入 3, 4 步), 如果要输入解释的话就需要 按键盘字母 i 进入 insert 模式 修改最上面那行黄色合并信息,可以不修改 // 黄色内容为默认的合并信息; 按键盘左上角 & ...
- el-transfer增加拖拽功能
el-transfer增加拖拽排序,左右互相拖拽功能: npm i sortablejs <template> <el-transfer ref="transfer&quo ...
- 24张图攻克border-image
大家好,我是半夏,一个刚刚开始写文的沙雕程序员.如果喜欢我的文章,可以关注 点赞 加我微信:frontendpicker,一起学习交流前端,成为更优秀的工程师-关注公众号:搞前端的半夏,了解更多前端知 ...
- Django与socket
Web框架本质是socket 各种socket一般都遵循wsgi协议 Django里面没有socket Django映射到Web框架,用了一个别人的socket:wsgiref 所以:django默认 ...
- rabbitmq 安装延时队列插件rabbitmq-delayed-message-exchange
1.下载rabbitmq-delayed-message-exchange(注意版本对应) 链接:https://github.com/rabbitmq/rabbitmq-delayed-messag ...
- spring 拦截器流程 HandlerInterceptor AsyncHandlerInterceptor HandlerInterceptorAdapter
HandlerInterceptor源码 3种方法: preHandle:拦截于请求刚进入时,进行判断,需要boolean返回值,如果返回true将继续执行,如果返回false,将不进行执行.一般用于 ...
- 弃用!Github 上用了 Git.io 缩址服务的都注意了
GitHub 是面向开源及私有软件项目的托管平台,因为只支持 Git 作为唯一的版本库格式进行托管,故名 GitHub.对程序员来说,GitHub 可以说是开源精神之所系.在 GitHub 任何职业程 ...
- uniapp复制到剪贴板
uni.setClipboardData() ; 例: 给元素添加点击事件 <view @click="doCopy()">复制</view> 复制方法 d ...
- zabbix5.0报错PHP时区未设置(配置参数"date.timezone")
解决办法 : #1.编辑文件/etc/opt/rh/rh-php72/php-fpm.d/zabbix.conf,取消注释并设置为所在地时区 vim /etc/opt/rh/rh-php72/php- ...