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. 概述 从本文你可以学习到: 什 ...
随机推荐
- 如何解决excel工作簿保护密码
自己的excel文档设置了“保护工作簿”密码,但是密码又忘记了,怎么办? 如果你会编写代码,那么这个问题非常好解决.Excel内置功能不能解决的事儿,自己编写一段代码或许就可以搞定了. 第一步,大家已 ...
- leetcode 回文二叉树
C++最简单的方法,遍历存在vector<int> ivec容器中,然后头尾对应比较 O(n)时间,O(n)空间 /** * Definition for singly-linked li ...
- ubuntu通过windows下的ccproxy代理上网
网上教程很多,需要注意的是将ubuntu的ip和windows的Ip设置到同一个网段,即子网掩码是1的对应的部分要相同.由于没有配置到同一个网段,折腾了我好久.
- linux配置ssh公钥认证,打通root用户的免密码输入的scp通道
1.ssh-keygen ssh-keygen是unix-like系统的一个用来生成.管理ssh公钥和私钥的工具. 2.用法 常用的重要的选项有: -b num 指定生成多少比特长度的key,单位 ...
- 阶段3 1.Mybatis_01.Mybatis课程介绍及环境搭建_06.mybatis的环境搭建
创建实体类,实现Serializable接口 属性名和数据库的字段名保持一致 Date字段需要导入包 生成gettter和setter,再生成一个ToString的方法 创建持久层Dao 创建接口 里 ...
- 2018.03.28 python-pandas groupby使用
groupby 分组统计 1.根据某些条件将数据分组 2.对每个组独立应用函数 3.将结果合并到一个数据结构中 Dataframe在行或列上分组,将一个函数应用到各个分组并产生一个新值,然后函数执行结 ...
- Eclipse."Courier New"字体
1.Win 7 下eclipse添加Courier New字体 - 彦帅的博客.html(https://blog.csdn.net/theblackbeard/article/details/525 ...
- 根据select的option文本选中对应的选项
function selectByOptionTxt(obj,txt){ var optionArr = $(obj).find("option"); for(var i=0;i& ...
- SwipeRefreshLayout和RecyclerView类
1 SwipeRefreshLayout和RecyclerView之间的关系 内容栏上下滚动是RecyclerView控制的,只有当内容栏滑动到最顶上时,再也拉不动了的时候,这个时候将动作交给Swip ...
- mysql日志信息查看与设置mysql-bin
查看 sql查询记录 日志是否开启 SHOW GLOBAL VARIABLES LIKE '%general_log%' 二进制日志 是否开启 SHOW GLOBAL VARIABLES LIKE ...