hashmap底层原理_Java集合 - HashMap原理(一) 概念和底层架构
HashMap在Java开发中使用的非常频繁,可以说仅次于String,可以和ArrayList并驾齐驱,准备用几个章节来梳理一下HashMap。我们还是从定义一个HashMap开始。
HashMap mapData = new HashMap<>();
我们从此处进入源码,逐步揭露HashMap
/** * Constructs an empty HashMap with the default initial capacity * (16) and the default load factor (0.75). */public HashMap() { this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted}
我们发现了两个变量loadFactor和DEFAULT_LOAD_FACTOR,从命名方式来看:因为没有接收到loadFactor参数,从而将某个默认值赋值给了loadFactor。这两变量到底是什么意思,还有无其他变量?
其实HashMap中定义的静态变量和成员变量很多,我们看一下
//静态变量static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16static final int MAXIMUM_CAPACITY = 1 << 30;static final float DEFAULT_LOAD_FACTOR = 0.75f;static final int TREEIFY_THRESHOLD = 8;static final int UNTREEIFY_THRESHOLD = 6;static final int MIN_TREEIFY_CAPACITY = 64;
//成员变量transient Node[] table;transient Set> entrySet;transient int size;transient int modCount;int threshold;final float loadFactor;
共有6个静态变量,都设置了初始值,且被final修饰,叫常量更合适,它们的作用其实也能猜出来,就是用于成员变量的默认值设定以及方法中相关的条件判断等情况。
共有6个成员变量,除这些成员变量外,还有一个重要概念capacity,我们主要说一下table,entrySet,capacity, size,threshold,loadFactor,我们我们简单解释一下它们的作用。
1. table变量
table变量为HashMap的底层数据结构,用于存储添加到HashMap中的Key-value对,是一个Node数组,Node是一个静态内部类,一种数组和链表相结合的复合结构,我们看一下Node类:
static class Node implements Map.Entry { final int hash; final K key; V value; Node next; Node(int hash, K key, V value, Node next) { this.hash = hash; this.key = key; this.value = value; this.next = next; } public final K getKey() { return key; } public final V getValue() { return value; } public final String toString() { return key + "=" + value; } public final int hashCode() { return Objects.hashCode(key) ^ Objects.hashCode(value); } public final V setValue(V newValue) { V oldValue = value; value = newValue; return oldValue; } public final boolean equals(Object o) { if (o == this) return true; if (o instanceof Map.Entry) { Map.Entry,?> e = (Map.Entry,?>)o; if (Objects.equals(key, e.getKey()) && Objects.equals(value, e.getValue())) return true; } return false; }}
若以乘机做比喻的话,那么你买票的身份证号就是(key),通过hash算法生成的(hash)值就相当于值机后得到的航班座位号;你自己自然就是(value),你旁边的座位、人就是下一个Node(next);这样的一个座位整体(包括所坐人员及其身份证号、座位号)就是一个table,这许多的table的构建的Node[] table,就构成了本次航班任务。
那么为什么要用到数组和链表结合的数据结构?
我们知道数组和链表都有其各自的优点和缺点,数组连续存储,寻址容易,插入删除操作相对困难;而链表离散存储,寻址相对困难,而插入删除操作容易;而HashMap结合了这两种数据结构,保留了各自的优点,又弥补了各自的缺点,当然链表长度太长的话,在JDK8中会转化为红黑树,红黑树在后面的TreeMap章节在讲解。
HashMap的结构图如下:
怎么解释这种结构呢?
还是以乘机为例来说明,假如购票系统比较人性化并取消了值机操作,购票按照年龄段进行了区分,方便大家旅途沟通交流,于是20岁以下共6个人的分为了一组在20A20F,2030岁共6个人分为一组在21A21F,3040岁共6个人分为一组在22A22F,4050岁共6个人分为一组在23A~23F。
这时我们如果要找20几岁的小姐姐,我们很容易知道去21排找,从21A开始往下找,应该就能很快找到。
从数据的角度看,按年龄段分组(通过hash算法得到hash值,不同年龄段hash值不同,相同年龄段hash值相同)后,将各年龄段中第一个坐到座位上的人放到数组table中,下一个人来的时候,将第一个人往里面挪,自己在数组里,并将next指向第一个人。
2. entrySet变量
entrySet变量为EntrySet实体,定义为变量可保证不重复多次创建,是一个Map.Entry的集合,Map.Entry是一个接口,Node类就实现了该接口,因此EntrySet中方法需要操作的数据就是HashMap的Node实体。
public Set> entrySet() { Set> es; return (es = entrySet) == null ? (entrySet = new EntrySet()) : es;}
3. capacity
capacity并不是一个成员变量,但HashMap中很多地方都会使用到这个概念,意思是容量,很好理解,在前面的文中提到了两个常量都与之相关
/** * The default initial capacity - MUST be a power of two(必须为2的幂次). * 默认容量16,举例:飞机上正常的座位所对应的人员数量, */static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16/** * The maximum capacity, used if a higher value is implicitly specified * by either of the constructors with arguments. * MUST be a power of two <= 1<<30(必须为2的幂次,且不能大于最大容量1,073,741,824). * 举例:紧急情况下,如救灾时尽可能快撤离人员,这个时候在保证安全的情况下(允许站立),能运输的人员数 */static final int MAXIMUM_CAPACITY = 1 << 30;
同时HashMap还具有扩容机制,容量的规则为2的幂次,即capacity可以是1,2,4,8,16,32...,怎么实现这种容量规则呢?
/** * Returns a power of two size for the given target capacity. */static final int tableSizeFor(int cap) { int n = cap - 1; n |= n >>> 1; n |= n >>> 2; n |= n >>> 4; n |= n >>> 8; n |= n >>> 16; return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;}
用该方法即可找到传递进来的容量的最近的2的幂次,即
cap = 2, return 2;
cap = 3, return 4;
cap = 9, return 16;
...
大家可以传递值进去自己算一下,先cap-1操作,是因为当传递的cap本身就是2的幂次情况下,假如为4,不减去一最后得到的结果将是传递的cap的2倍。
我们来一行行计算一下:tableSizeFor(11),按规则最后得到的结果应该是16
//第一步:n = 10,转为二进制为00001010int n = cap - 1;//第二步:n右移1位,高位补0(10进制:5,二进制:00000101),并与n做或运算(有1为1,同0为0),然后赋值给n(10进制:15,二进制:00001111)n |= n >>> 1;//第三步:n右移2位,高位补0(10进制:3,二进制:00000011),并与n做或运算(有1为1,同0为0),然后赋值给n(10进制:15,二进制:00001111)n |= n >>> 2;//第四步:n右移4位,高位补0(10进制:0,二进制:00000000),并与n做或运算(有1为1,同0为0),然后赋值给n(10进制:15,二进制:00001111)n |= n >>> 4;//第五步:n右移8位,高位补0(10进制:0,二进制:00000000),并与n做或运算(有1为1,同0为0),然后赋值给n(10进制:15,二进制:00001111)n |= n >>> 8;//第六步:n右移16位,高位补0(10进制:0,二进制:00000000),并与n做或运算(有1为1,同0为0),然后赋值给n(10进制:15,二进制:00001111)n |= n >>> 16;//第七步:return 15+1 = 16;return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
最终的结果正如预期,算法很牛逼啊,ヽ(ー_ー)ノ,能看懂,但却设计不出来。
4. size变量
size变量记录了Map中的key-value对的数量,在调用putValue()方法以及removeNode()方法时,都会对其造成改变,和capacity区分一下即可。
5. threshold变量和loadFactor变量
threshold为临界值,顾名思义,当过了临界值就需要做一些操作了,在HashMap中临界值“threshold = capacity * loadFactor”,当超过临界值时,HashMap就该扩容了。
loadFactor为装载因子,就是用来衡量HashMap满的程度,默认值为DEFAULT_LOAD_FACTOR,即0.75f,可通过构造器传递参数调整(0.75f已经很合理了,基本没人会去调整它),很好理解,举个例子:
100分的试题,父母只需要你考75分,就给你买一台你喜欢的电脑,装载因子就是0.75,75分就是临界值;如果几年后,试题的分数变成200分了,这个时候就需要你考到150分才能得到你喜欢的电脑了。
总结
本文主要讲解了HashMap中的一些主要概念,同时对其底层数据结构从源码的角度进行了分析,table是一个数据和链表的复合结构,size记录了key-value对的数量,capacity为HashMap的容量,其容量规则为2的幂次,loadFactor为装载因此,衡量满的程度,而threshold为临界值,当超出临界值时就会扩容。
----------------------------------------------------
转载自:https://www.cnblogs.com/LiaHon/p/11142958.html
hashmap底层原理_Java集合 - HashMap原理(一) 概念和底层架构相关推荐
- 框架实现修改功能的原理_JAVA集合框架的特点及实现原理简介
1.集合框架总体架构 集合大致分为Set.List.Queue.Map四种体系,其中List,Set,Queue继承自Collection接口,Map为独立接口 Set的实现类有:HashSet,Li ...
- copyonwritearraylist原理_Java集合干货——CopyOnWriteArrayList源码分析
前言 CopyOnWriteArrayList是一个线程安全集合,原理简单说就是:在保证线程安全的前提下,牺牲掉写操作的效率来保证读操作的高效.所谓CopyOnWrite就是通过复制的方式来完成对数据 ...
- set java底层实现_Java:List,Map,Set底层实现
1 ArrayList实现原理要点概括2 参考文献:3 http://zhangshixi.iteye.com/blog/674856l 4 https://www.cnblogs.com/leesf ...
- hashmap实现原理_Java中HashMap底层实现原理(JDK1.8)源码分析
在JDK1.6,JDK1.7中,HashMap采用位桶+链表实现,即使用链表处理冲突,同一hash值的链表都存储在一个链表里.但是当位于一个桶中的元素较多,即hash值相等的元素较多时,通过key值依 ...
- java concurrentmap原理_Java集合番外篇 -- ConcurrentHashMap底层实现和原理
概述 距离上一次集合篇结束已经过了好久了, 之前说要写一下番外,但是太忙了,总也找不出相对松散的时间,也有点静不下心来,最近花了点时间,于是便有了这篇博客. 在开始之前先介绍一个算法, 这个算法和Co ...
- java treeset原理_Java集合 --- TreeSet底层实现和原理(源码解析)
概述 文章的内容基于JDK1.7进行分析,之所以选用这个版本,是因为1.8的有些类做了改动,增加了阅读的难度,虽然是1.7,但是对于1.8做了重大改动的内容,文章也会进行说明. TreeSet实现了S ...
- java list原理_Java集合:ArrayList的实现原理
目录: 一. ArrayList概述: ArrayList是基于数组实现的,是一个动态数组,其容量能自动增长,类似于C语言中的动态申请内存,动态增长内存. ArrayList不是线程安全的,只能用在单 ...
- java 迭代器的原理_Java集合框架迭代器Iterator实现原理解析
使用循环遍历集合 普通for循环 for(int i=0;i<10;i++){} 增强for循环 for(String str:list){} 什么是迭代器Iterator Iterator是J ...
- java 递归原理_Java中递归原理实例分析
本文实例分析了Java中递归原理.分享给大家供大家参考.具体分析如下: 解释:程序调用自身的编程技巧叫做递归. 程序调用自身的编程技巧称为递归( recursion).递归做为一种算法在程序设计语言中 ...
最新文章
- mysql 排序取前4_MySQL时间段分组排序后取前10的问题?
- OSI[七层]与TCP/IP[四层]模型简述简图
- Python文件的使用
- C# WinForm 通过URL取得服务器上的某图片文件到本地
- fastdfs上传文件_SpringBoot+FastDFS搭建分布式文件系统
- diff 命令,防止遗忘
- C#关闭一个窗口的同时打开另一个窗口
- BAPI:BAPI_CONTRACT_CREATE(内部合同创建)
- Serverless的理解
- Vue 3.2 发布了,那尤雨溪是怎么发布 Vue.js 的?
- Tomcat提示“XDB 的服务器 localhost 要求用户名和密码”
- 在SPA应用中利用JWT进行身份验证
- 递推+高精度——蜜蜂路线(洛谷 P2437)
- webwork在freemarker中使用iterator
- 简述使jdbc连接mysql数据库,关于JDBC的六个步骤
- 一阶低通滤波器方程_一阶RC低通滤波器和RC高通滤波器简介-模拟/电源-与非网...
- arccos c语言,[蓝桥杯][算法提高VIP]求arccos值 (C语言代码)
- Centos8上安装中文字符集zh_CN.UTF-8
- 第四章第四节数据资产盘点-数据资产梳理
- CNN网络模型大总结【持续更新中...】
热门文章
- asp点击链接数字加1代码_Asp.Net Core使用TinyMCE富媒体编辑器
- python 阿里云短信接口_阿里云短信PythonSDK的用法
- sql 判断记录是否存在_判断数据库是否存在该条记录,count(0) or limit
- 360 mysql无法启动_安装MySQL 5后无法启动(不能Start service)解决方法小结
- mysql 类型_MySQL-约束类型
- CTF-攻防世界-reverse进阶-srm-50;(巨详细)
- dz论坛ucenter打不开mysql_Discuz论坛搬家 ucenter info:can not connect to MySQL server解决办法...
- Python中列表和字符串的反转
- Python回调函数用法实例
- oracle中存在函数吗,Oracle中的函数