Java HashMap 实现概况及容量
原文链接:https://mp.weixin.qq.com/s/JcnSOGKQlDgaTTFKZFbXnA?scene=25#wechat_redirect
1 简单说说 HashMap 的底层原理?
当我们往 HashMap 中 put 元素时,先根据 key 的 hash 值得到这个 Entry 元素在数组中的位置(即下标),然后把这个 Entry 元素放到对应的位置中,如果这个 Entry 元素所在的位子上已经存放有其他元素就在同一个位子上的 Entry 元素以链表的形式存放,新加入的放在链头,从 HashMap 中 get Entry 元素时先计算 key 的 hashcode,找到数组中对应位置的某一 Entry 元素,然后通过 key 的 equals 方法在对应位置的链表中找到需要的 Entry 元素,所以 HashMap 的数据结构是数组和链表的结合,此外 HashMap 中 key 和 value 都允许为 null,key 为 null 的键值对永远都放在以 table[0] 为头结点的链表中。
之所以 HashMap 这么设计的实质是由于数组存储区间是连续的,占用内存严重,故空间复杂度大,但二分查找时间复杂度小(O(1)),所以寻址容易而插入和删除困难;而链表存储区间离散,占用内存比较宽松,故空间复杂度小,但时间复杂度大(O(N)),所以寻址困难而插入和删除容易;所以就产生了一种新的数据结构叫做哈希表,哈希表既满足数据的查找方便,同时不占用太多的内容空间,使用也十分方便,哈希表有多种不同的实现方法,HashMap 采用的是链表的数组实现方式。
特别说明,对于 JDK 1.8 开始 HashMap 实现原理变成了数组+链表+红黑树的结构,数组链表部分基本不变,红黑树是为了解决哈希碰撞后链表索引效率的问题,所以在 JDK 1.8 中当链表的节点大于 8 个时就会将链表变为红黑树。区别是 JDK 1.8 以前碰撞节点会在链表头部插入,而 JDK 1.8 开始碰撞节点会在链表尾部插入,对于扩容操作后的节点转移 JDK 1.8 以前转移前后链表顺序会倒置,而 JDK 1.8 中依然保持原序。
2 HashMap 默认的初始化长度是多少?为什么默认长度和扩容后的长度都必须是 2 的幂?
在 JDK 中默认长度是 16(在 Android SDK 中的 HashMap 默认长度为 4),并且默认长度和扩容后的长度都必须是 2 的幂。因为我们可以先看下 HashMap 的 put 方法核心,如下:
public V put(K key, V value) {......//计算出 key 的 hash 值int hash = hash(key);//通过 key 的 hash 值和当前动态数组的长度求当前 key 的 Entry 在数组中的下标int i = indexFor(hash, table.length);......}//最核心的求数组下标方法static int indexFor(int h, int length) {// assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";return h & (length-1);}
可以看到获取数组索引的计算方式为 key 的 hash 值按位与运算数组长度减一,为了说明长度尽量是 2 的幂的作用我们假设执行了 put("android", 123); 语句且 "android" 的 hash 值为 234567,二进制为 111001010001000111,然后由于 HashMap 默认长度为 16,所以减一后的二进制为 1111,接着两数做按位与操作二进制结果为 111,即十进制的 7,所以 index 为 7,可以看出这种按位操作比直接取模效率要高。
如果假设 HashMap 默认长度不是 2 的幂,譬如数组长度为 6,减一的二进制为 101,与 111001010001000111 按位与操作二进制 101,此时假设我们通过 put 再放一个 key-value 进来,其 hash 为 111001010001000101,其与 101 按位与操作后的二进制也为 101,很容易发生哈希碰撞,这就不符合 index 的均匀分布了。
通过上面两个假设例子可以看出 HashMap 的长度为 2 的幂时减一的值的二进制位数一定全为 1,这样数组下标 index 的值完全取决于 key 的 hash 值的后几位,因此只要存入 HashMap 的 Entry 的 key 的 hashCode 值分布均匀,HashMap 中数组 Entry 元素的分部也就尽可能是均匀的(也就避免了 hash 碰撞带来的性能问题),所以当长度为 2 的幂时不同的 hash 值发生碰撞的概率比较小,这样就会使得数据在 table 数组中分布较均匀,查询速度也较快。不过即使负载因子和 hash 算法设计的再合理也免不了哈希冲突碰撞的情况,一旦出现过多就会影响 HashMap 的性能,所以在 JDK 1.8 中官方对数据结构引入了红黑树,当链表长度太长(默认超过 8)时链表就转为了红黑树,而红黑树的增删改查都比较高效,从而解决了哈希碰撞带来的性能问题。
Java HashMap 实现概况及容量的更多相关文章
- [翻译]Java HashMap工作原理
大部分Java开发者都在使用Map,特别是HashMap.HashMap是一种简单但强大的方式去存储和获取数据.但有多少开发者知道HashMap内部如何工作呢?几天前,我阅读了java.util.Ha ...
- Java学习笔记(二二)——Java HashMap
[前面的话] 早上起来好瞌睡哈,最近要注意一样作息状态. HashMap好好学习一下. [定义] Hashmap:是一个散列表,它存储的内容是键值对(key——value)映射.允许nul ...
- java集合框架之java HashMap代码解析
java集合框架之java HashMap代码解析 文章Java集合框架综述后,具体集合类的代码,首先以既熟悉又陌生的HashMap开始. 源自http://www.codeceo.com/arti ...
- 【转】Java HashMap工作原理(好文章)
大部分Java开发者都在使用Map,特别是HashMap.HashMap是一种简单但强大的方式去存储和获取数据.但有多少开发者知道HashMap内部如何工作呢?几天前,我阅读了java.util.Ha ...
- 【转】Java HashMap 源码解析(好文章)
.fluid-width-video-wrapper { width: 100%; position: relative; padding: 0; } .fluid-width-video-wra ...
- 转:Java HashMap实现详解
Java HashMap实现详解 转:http://beyond99.blog.51cto.com/1469451/429789 1. HashMap概述: HashMap是基于哈希表的M ...
- JAVA HashMap详细介绍和示例
http://www.jb51.net/article/42769.htm 我们先对HashMap有个整体认识,然后再学习它的源码,最后再通过实例来学会使用HashMap. 第1部分 HashMa ...
- 自学Java HashMap源码
自学Java HashMap源码 参考:http://zhangshixi.iteye.com/blog/672697 HashMap概述 HashMap是基于哈希表的Map接口的非同步实现.此实现提 ...
- Java HashMap工作原理及实现
Java HashMap工作原理及实现 2016/03/20 | 分类: 基础技术 | 0 条评论 | 标签: HASHMAP 分享到:3 原文出处: Yikun 1. 概述 从本文你可以学习到: 什 ...
随机推荐
- 九:flask-response响应
1.如果返回的是个合法的响应对象,则直接返回 同时,也可以在response里面做一些操作,比如增加cookie 2.如果返回的是一个字符串,那么flask会重新创建一个werkzeug,wrappe ...
- Jmeter之线程组(默认)
Jmeter中的采样器必须要基于线程组. 一.添加线程组 在测试计划上右键,然后选择,如下图: 二.线程组界面 三.线程组界面配置说明 1.名称:线程组自定义名称: 2.注释:添加的一些备注说明信息, ...
- Java连接Hive使用Zookeeper的方式
Java连接Hive的方式就是通过JDBC的方式来连接,URL为jdbc:hive2://host:port/db;principal=X@BIGDATA.COM等,这种方式是直接连接HiveServ ...
- Python中的Django框架中prefetch_related()函数对数据库查询的优化
实例的背景说明 假定一个个人信息系统,需要记录系统中各个人的故乡.居住地.以及到过的城市.数据库设计如下: Models.py 内容如下: ? 1 2 3 4 5 6 7 8 9 10 11 12 1 ...
- 中国MOOC_面向对象程序设计——Java语言_期末考试编程题_1细胞自动机
期末考试编程题 返回 这是期末考试的编程题 温馨提示: 1.本次考试属于Online Judge题目,提交后由系统即时判分. 2.学生可以在考试截止时间之前提交答案,系统将取其中的最高分作为最终成 ...
- Go语言入门篇-Golang之文本编码处理
Golang之文本编码处理
- python 并发编程 多进程 守护进程
一 守护进程 主进程创建子进程目的是:主进程有一个任务需要并发执行,那开启子进程帮我并发执行任务 主进程创建子进程,然后将该进程设置成守护自己的进程 关于守护进程需要强调两点: 其一:守护进程会在主进 ...
- ubuntu 虚拟机安装
ubuntu16.04.5 LTS 安装 1.下载ubuntu镜像 打开ubuntu官网镜像地址https://launchpad.net/ubuntu/+cdmirrors 在上面中搜索chin ...
- [转帖]挖洞经验 | 获取Facebook Marketplace卖家精确地理位置信息
挖洞经验 | 获取Facebook Marketplace卖家精确地理位置信息 https://www.freebuf.com/vuls/202820.html 知识就是力量 5000刀的一个漏洞. ...
- 【监控笔记】【1.4】Pssdiag和Sqldiag管理器
--没有实操过,有点复杂,先写上以后有用到再深入研究 统计与诊断数据是任何 SQL故障修复工作的关键所在. 如果没有掌握这些数据,就无法确定数据性能问题的根源.数据表的瓶颈可能并不是由索引问题造成的: ...