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集合框架的源码以 ...
随机推荐
- CSS 动画 : 创建 3D 立方体
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- android studio 如何把依赖导出成 jar
反编译工具 dex-tools-2.1-SNAPSHOT 第一步 用一个普通的app工程,引用所有的库,然后生成apk文件 第二步 把apk文件,改扩展名为zip,解压后,里面有几个*.dex文件,拷 ...
- Go-数组-实现队列
package main import ( "errors" "fmt" ) // 队列 // 特征: // 1. 按照元素的添加顺序排序,并且容量固定 // ...
- [转帖]如何使用coredump
一.coredump 当用户态进程出现异常后,在该进程的执行目录下生成对应的coredump文件,如果我们想将coredump生成的位置做改变,就需要如下设置. echo "/home/co ...
- [转帖]TiDB之修改root密码
https://www.modb.pro/db/337530 当忘记TiDB root 密码时,可以通过设置skip-grant-table参数来跳过密码验证,登录成功以后再修改root密码. 方法一 ...
- [转帖]GC Ergonomics间接引发的锁等待超时问题排查分析
https://www.cnblogs.com/micrari/p/8831834.html 1. 问题背景 上周线上某模块出现锁等待超时,如下图所示:我虽然不是该模块负责人,但出于好奇,也一起帮忙排 ...
- [转帖]内存随机访问也比顺序慢,带你深入理解内存IO过程
https://zhuanlan.zhihu.com/p/86513504 平时大家都知道内存访问很快,今天来让我们来思考两个问题: 问题1: 内存访问一次延时到底是多少?你是否会进行大概的估算? 例 ...
- add_argument()方法基本参数使用
selenium做web自动化时我们想要通过get打开一个页面之前就设置好一些基本参数,需要 通过add_argument()方法来设置,下面以一个简单的不展示窗口为例. option = webdr ...
- 责任链和策略设计模式-基于Java编程语言
作者:京东物流 钟磊 1 前言 最近在梳理接口逻辑的时候发现,代码中使用的策略和责任链设计模式给我留下了非常深刻的印象.一个业务逻辑流程通常非常适合使用责任链和策略设计模式来实现,因为一个业务需求通常 ...
- 【计算几何,数学】7.14 T3 @ xdfz
Problem Link 给定 \(n\) 个球和一个点 \(P\),求点 \(P\) 到这些球的交内一点的距离的最小值.保证有解.\(n\le 10^6\). 和最小圆覆盖一个套路.考虑维护一个当前 ...