Java语法专题3: HashMap
- 合集目录
- Java语法专题3: HashMap
谈谈 HashMap 的特性
- 存储KV键值对, 实现快速存取, key和value都允许为null. key值唯一, 重复则覆盖.
- key为null时, 内部使用的是0这个值
- 底层数据结构是数组
- 非线程安全
- 无顺序
谈谈 HashMap 的工作机制
谈谈 HashMap 的底层原理
- 从 JDK8 开始采用数组 + 链表 + 红黑树的数据结构, 一直到JDK11基本没变
- 存储对象时, 先对键做 hash 计算(基于hashCode), 得到它在bucket数组中的位置, 存储Entry对象
- 获取对象时, 同样地先定位到bucket的位置, 再通过键对象的equals()方法找到正确的键值对, 返回值对象
HashMap 中 get 是如何实现的
先对键做 hash 计算(基于hashCode), 得到它在bucket数组中的位置, 然后在这个bucket的树或者链表中遍历查找, 直到找到满足 equals 的节点.
HashMap 中 put 是如何实现的
- 计算关于key的hashcode(与Key.hashCode的高16位做异或运算)
- 散列表为空时调用resize()初始化散列表
- 如果没有发生碰撞,直接添加元素到散列表
- 如果发生了碰撞(hashCode值相同), 进行三种判断
- 若key地址相同或者equals后内容相同,则替换旧值
- 如果是红黑树结构,就调用树的插入方法
- 如果是链表结构, 循环遍历: 若遍历到有节点与插入元素的哈希值和内容相同则进行覆盖; 若无相同则尾插法进行插入,插入之后判断链表个数是否到达变成红黑树的阈值8.
- 如果桶容量超过阀值,则resize进行扩容
HashMap 的大小超过了负载因子定义的容量会怎样处理?
HashMap 的扩容是如何工作的
扩容的时机
HashMap 在初始化数组Table和当数组Table容量达到阈值时, 在putVal函数中触发扩容
阈值的计算
size > load factor * capacity
扩容的过程
扩容需要重新分配一个新数组, 新数组长度是旧数组的2倍, 遍历旧数组,将元素重新hash分配到新结构中去.
HashMap 中 hash 函数是怎么实现的?
hash的计算方法是: 对key对象计算hashCode, 再将 hashCode 与 hashCode 的高16bit做XOR. 如果key是null, 则直接返回0.
以下详细说明
hash 函数的代码
hash计算是 HashMap 实现机制中很重要的一环, 对任何对象(包括null), 返回一个int类型的hash值. 其实现的代码为
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
Object 的 hashCode()
底层的实现, 实际上是 Object 的 hashCode() 函数, 这个函数是native的, 由 C/C++ 提供
public native int hashCode();
hashCode()这个函数有三个特性:
- 对同一个对象多次调用, 返回的结果一定相同
- 对满足
equals(Object)的两个对象分别调用, 返回的结果一定相同 - 对于不满足
equals(Object)的两个对象分别调用, 返回的结果不一定不同
为什么要对 hashCode() 的结果做高16位的异或运算
h >>> 16是无符号位移运算, Java的int是4个字节16bit, 这个运算将高16bit移动到低16bit- HashMap 默认初始化容量为16, 每次自动扩展或者是手动初始化容量时, 必须是2的N次幂
- 将结果与自身的高16bit进行XOR运算, 因为HashMap的增长使用容量是2的N次幂, 将这个作为掩码, 如果hash值只在比这个掩码更高位的bit上有变化, 那么产生的结果总是碰撞的. 这时候需要将高位的变化体现到低位上降低碰撞的概率
- 位移和XOR是代价最低的运算. XOR some shifted bits in the cheapest possible way to reduce systematic lossage.
在低位产生大量碰撞的例子
如果不做高位异或会造成碰撞的例子是均匀增长的浮点数, 参考以下代码
public static void main(String[] args) {
float i;
for (i = 0.1f; i < 1000; i++) {
System.out.println(Float.valueOf(i).hashCode());
}
}
i的初始值可以修改为0, 0.1f以及其他值, 观察输出, 可以看到在某些小数部分的情况下(例如0和0.1f), 最低位的结果是固定的0, 2, 4, 6, 8. 如果将上面的循环步长调整为5,
public static void main(String[] args) {
float i;
for (i = 0.1f; i < 1000; i = i + 5) {
System.out.println(Float.valueOf(i).hashCode());
}
}
可以看到在输出的后半部分, 结果的个位全部为8
...
1146963558
1147045478
1147127398
1147209318
1147291238
1147373158
1147455078
如果在实际应用中正好碰到了这种情况, HashMap 的效率将变得非常低. 如果是 JDK7 没有红黑树结构, 时间复杂度就是O(n), JDK8红黑树结构下对应的时间复杂度也是O(logn).
默认容量为什么是2的N次幂?
为了数据的均匀分布,减少哈希碰撞
确定数组位置是用的位运算, 若数据不是2的次幂, 则会增加哈希碰撞的次数和浪费数组空间
HashMap和HashTable的区别?
相同点
都是存储key-value键值对
不同点
- HashMap 允许 Key-value为null, HashTable不允许
- HashMap 线程不安全, HashTable 线程安全的
- HashMap 继承于AbstractMap, HashTable继承于Dictionary
- HashMap 的迭代器(Iterator)是fail-fast迭代器, 而 Hashtable 的enumerator迭代器不是fail-fast的. 所以当有其它线程改变了 HashMap 的结构(增加或者移除元素), 将会抛出 ConcurrentModificationException.
- 容量的初始值和增加方式不一样: HashMap 默认的容量是16, 扩容时每次翻倍. Hashtable 默认容量是11, 扩容时每次将容量变为(原容量 x 2 + 1)
- Hash值算法不同: HashMap使用自定义的hash方法, Hashtable 直接采用的key的hashCode()
谈谈 HashMap 的 loadFactor 参数的作用
loadFactor 是 HashMap 的负载因子, 表示数组的使用率上限. 默认为0.75, 当容纳的元素已经达到数组长度的75%时, 就会进行扩容
HashMap的缺点, JDK8 为什么引入红黑树?
JDK7的 HashMap 实现是数组+链表, 因为哈希计算存在碰撞的可能性, 当大量的元素都存放到同一个桶中时, 就会形成一条很长的链表, 这时 HashMap 读取时就需要遍历这个链表, 时间复杂度就是 O(n). JDK8 引入红黑树是为了解决这个问题, 将查找时间复杂度为优化到 O(logn).
Java语法专题3: HashMap的更多相关文章
- Java语法专题1: 类的构造顺序
合集目录 Java语法专题1: 类的构造顺序 问题 下面的第二个问题来源于Oracle的笔试题, 非常经典的一个问题, 我从07年开始用了十几年. 看似简单, 做对的比例不到2/10. 描述一下多级继 ...
- Java语法专题2: 类变量的初始化顺序
合集目录 Java语法专题2: 类变量的初始化顺序 问题 这也是Java面试中出镜率很高的基础概念问题 描述一下多级继承中字段初始化顺序 描述一下多级继承中类变量初始化顺序 写出运行以下代码时的控制台 ...
- Java基础专题
Java后端知识点汇总——Java基础专题 全套Java知识点汇总目录,见https://www.cnblogs.com/autism-dong/p/11831922.html 1.解释下什么是面向对 ...
- Java语法知识总结
一:java概述: 1991 年Sun公司的James Gosling等人开始开发名称为 Oak 的语言,希望用于控制嵌入在有线电视交换盒.PDA等的微处理器: 1994年将Oak语言更名为Java: ...
- 深入理解java虚拟机(十二) Java 语法糖背后的真相
语法糖(Syntactic Sugar),也叫糖衣语法,是英国计算机科学家彼得·约翰·兰达(Peter J. Landin)发明的一个术语.指的是,在计算机语言中添加某种语法,这些语法糖虽然不会对语言 ...
- Java 语法清单
Java 语法清单 Java 语法清单翻译自 egek92 的 JavaCheatSheet,从属于笔者的 Java 入门与实践系列.时间仓促,笔者只是简单翻译了些标题与内容整理,支持原作者请前往 ...
- Java语法
java语法: 一个java程序可以说是一系列对象的集合,而这些对象都要通过调用彼此的方法来协同工作. 对象: 对象是一个实例,例如:一只猫,它是一个对象,有状态和行为.它的状态状态有:颜色,名字,品 ...
- (转)Java集合框架:HashMap
来源:朱小厮 链接:http://blog.csdn.net/u013256816/article/details/50912762 Java集合框架概述 Java集合框架无论是在工作.学习.面试中都 ...
- Java语法糖1:可变长度参数以及foreach循环原理
语法糖 接下来几篇文章要开启一个Java语法糖系列,所以首先讲讲什么是语法糖.语法糖是一种几乎每种语言或多或少都提供过的一些方便程序员开发代码的语法,它只是编译器实现的一些小把戏罢了,编译期间以特定的 ...
- Java集合框架:HashMap
转载: Java集合框架:HashMap Java集合框架概述 Java集合框架无论是在工作.学习.面试中都会经常涉及到,相信各位也并不陌生,其强大也不用多说,博主最近翻阅java集合框架的源码以 ...
随机推荐
- JavaScript合并多个数组
工作中经常会对数组进行合并,稍微总结一下常用的方法: concat JavaScript原生自带的函数,用法如下: let arr1 = [3, 5, 7]; let arr2 = [4, 78, 7 ...
- 【SHELL】命令补全
# 指定文件 dodo_path=/home/skull/work/scripts/dodo echo "hello skull" ## COMP_WORDS 是一个 bash 内 ...
- 【Git】Git与Repo入门
[来源]https://www.cnblogs.com/angeldevil/archive/2013/11/26/3238470.html
- [转帖]crash工具分析Kdump下vmcore文件常用命令总结(三)(实例易懂)
一.简介 本文主要介绍使用crash工具对kdump生成的vmcore文件进行分析,解析常见的crash命令,前面已讲述两章关于Kdump的内容,读者感兴趣可以点击下面的链接: 1.Kdump调试机理 ...
- [转帖]Elasticsearch-索引性能调优
1:设置合理的索引分片数和副本数 索引分片数建议设置为集群节点的整数倍,初始数据导入时副本数设置为 0,生产环境副本数建议设置为 1(设置 1 个副本,集群任意 1 个节点宕机数据不会丢失:设置更多副 ...
- [转帖]RPC 框架架构设计
github地址:https://github.com/xiaojiesir/mini-rpc RPC 又称远程过程调用(Remote Procedure Call),用于解决分布式系统中服务之间的调 ...
- MAT的简单学习
背景说明 Java遇到问题之后比较浅层的跟踪解决办法: jps 查看进程的main jar包 对应的进程信息 jstack 查看 堆栈信息 top -Hp PID 实时查看具体的CPU进程信息. 如果 ...
- sed 删除部分行以及删除包含某些行的命令
sed的简单学习 前言: 最近进行mysql数据库的备份恢复操作,发现source 命令执行时数据库表的速度非常缓慢, 本来想通过这种方式处理一下,能够减少数据备份的处理. 删除包含内容的信息 sed ...
- 基于javaPoet的缓存key优化实践
一. 背景 在一次系统opsreview中,发现了一些服务配置了@Cacheable注解.@cacheable 来源于spring cache框架中,作用是使用aop的方式将数据库中的热数据缓存在re ...
- 重新学习一下new Date()
new Date()你知道多少 很多小伙伴可能都知道, Date是js中的一个内置对象,用于处理日期和时间. 当你调用 new Date() 时,它会创建一个新的日期(Date) 对象. 表示当前本地 ...