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集合框架的源码以 ...
随机推荐
- Chrome/Edge 设置黑色主题
Chrome chrome://flags/#enable-force-dark Edge edge://flags/#enable-force-dark
- [转帖]Redhat 8 磁盘调度策略变化:NOOP改为NONE
说明 在 redhat 4/5/6/7版本中的NOOP调度策略,从8开始修改为NONE,官方解释: none Implements a first-in first-out (FIFO) schedu ...
- [转帖]一问带你掌握通过storcli做RAID
因为系统不支持直接做raid,所以需要使用storcli这个工具来操作.首先把工具上传到服务器任意目录,并使用命令chmod +x storcli64修改文件权限为可执行. 另外可通过命令ln -s ...
- StorageClass 简单学习
StorageClass 简单学习 学习资料来源 https://www.jianshu.com/p/5e565a8049fc https://zhuanlan.zhihu.com/p/2895019 ...
- Redis做Mybatis的二级缓存
Redis做mybatis的二级缓存 作用提升速度,保证多台服务器访问同一数据库时不会崩 注意:保证本地有下载redis且已经打开,否则无法使用. [本文只讲述了实现步骤,并没有原理讲解] 保证有导入 ...
- Linux线程间交互
前言 上一篇说过,系统会为线程mmap一块内存,每个线程有自己的私有栈,使用局部变量没啥问题.但是实际场景中不可避免的需要线程之间共享数据,这就需要确保每个线程看到的数据是一样的,如果大家都只需要读这 ...
- 深入浅出Java多线程(二):Java多线程类和接口
引言 大家好,我是你们的老伙计秀才!今天带来的是[深入浅出Java多线程]系列的第二篇内容:Java多线程类和接口.大家觉得有用请点赞,喜欢请关注!秀才在此谢过大家了!!! 在现代计算机系统中,多线程 ...
- Midjourney|文心一格prompt教程[Text Prompt(下篇)]:游戏、实物、人物、风景、动漫、邮票、海报等生成,终极模板教学
Midjourney|文心一格prompt教程[Text Prompt(下篇)]:游戏.实物.人物.风景.动漫.邮票.海报等生成,终极模板教学 场景6:游戏 Prompt 真的越长越好吗? 按照 Mi ...
- C++ Boost 异步网络编程基础
Boost库为C++提供了强大的支持,尤其在多线程和网络编程方面.其中,Boost.Asio库是一个基于前摄器设计模式的库,用于实现高并发和网络相关的开发.Boost.Asio核心类是io_servi ...
- C++ STL 标准模板库(排序/集合/适配器)算法
C++ 标准模板库STL,是一个使用模板技术实现的通用程序库,该库由容器container,算法algorithm,迭代器iterator,容器和算法之间通过迭代器进行无缝连接,其中所包含的数据结构都 ...