谈谈 HashMap 的特性

  1. 存储KV键值对, 实现快速存取, key和value都允许为null. key值唯一, 重复则覆盖.
  2. key为null时, 内部使用的是0这个值
  3. 底层数据结构是数组
  4. 非线程安全
  5. 无顺序

谈谈 HashMap 的工作机制

谈谈 HashMap 的底层原理

  1. 从 JDK8 开始采用数组 + 链表 + 红黑树的数据结构, 一直到JDK11基本没变
  2. 存储对象时, 先对键做 hash 计算(基于hashCode), 得到它在bucket数组中的位置, 存储Entry对象
  3. 获取对象时, 同样地先定位到bucket的位置, 再通过键对象的equals()方法找到正确的键值对, 返回值对象

HashMap 中 get 是如何实现的

先对键做 hash 计算(基于hashCode), 得到它在bucket数组中的位置, 然后在这个bucket的树或者链表中遍历查找, 直到找到满足 equals 的节点.

HashMap 中 put 是如何实现的

  1. 计算关于key的hashcode(与Key.hashCode的高16位做异或运算)
  2. 散列表为空时调用resize()初始化散列表
  3. 如果没有发生碰撞,直接添加元素到散列表
  4. 如果发生了碰撞(hashCode值相同), 进行三种判断
    • 若key地址相同或者equals后内容相同,则替换旧值
    • 如果是红黑树结构,就调用树的插入方法
    • 如果是链表结构, 循环遍历: 若遍历到有节点与插入元素的哈希值和内容相同则进行覆盖; 若无相同则尾插法进行插入,插入之后判断链表个数是否到达变成红黑树的阈值8.
  5. 如果桶容量超过阀值,则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()这个函数有三个特性:

  1. 对同一个对象多次调用, 返回的结果一定相同
  2. 对满足equals(Object)的两个对象分别调用, 返回的结果一定相同
  3. 对于不满足equals(Object)的两个对象分别调用, 返回的结果不一定不同

为什么要对 hashCode() 的结果做高16位的异或运算

  1. h >>> 16是无符号位移运算, Java的int是4个字节16bit, 这个运算将高16bit移动到低16bit
  2. HashMap 默认初始化容量为16, 每次自动扩展或者是手动初始化容量时, 必须是2的N次幂
  3. 将结果与自身的高16bit进行XOR运算, 因为HashMap的增长使用容量是2的N次幂, 将这个作为掩码, 如果hash值只在比这个掩码更高位的bit上有变化, 那么产生的结果总是碰撞的. 这时候需要将高位的变化体现到低位上降低碰撞的概率
  4. 位移和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的更多相关文章

  1. Java语法专题1: 类的构造顺序

    合集目录 Java语法专题1: 类的构造顺序 问题 下面的第二个问题来源于Oracle的笔试题, 非常经典的一个问题, 我从07年开始用了十几年. 看似简单, 做对的比例不到2/10. 描述一下多级继 ...

  2. Java语法专题2: 类变量的初始化顺序

    合集目录 Java语法专题2: 类变量的初始化顺序 问题 这也是Java面试中出镜率很高的基础概念问题 描述一下多级继承中字段初始化顺序 描述一下多级继承中类变量初始化顺序 写出运行以下代码时的控制台 ...

  3. Java基础专题

    Java后端知识点汇总——Java基础专题 全套Java知识点汇总目录,见https://www.cnblogs.com/autism-dong/p/11831922.html 1.解释下什么是面向对 ...

  4. Java语法知识总结

    一:java概述: 1991 年Sun公司的James Gosling等人开始开发名称为 Oak 的语言,希望用于控制嵌入在有线电视交换盒.PDA等的微处理器: 1994年将Oak语言更名为Java: ...

  5. 深入理解java虚拟机(十二) Java 语法糖背后的真相

    语法糖(Syntactic Sugar),也叫糖衣语法,是英国计算机科学家彼得·约翰·兰达(Peter J. Landin)发明的一个术语.指的是,在计算机语言中添加某种语法,这些语法糖虽然不会对语言 ...

  6. Java 语法清单

      Java 语法清单 Java 语法清单翻译自 egek92 的 JavaCheatSheet,从属于笔者的 Java 入门与实践系列.时间仓促,笔者只是简单翻译了些标题与内容整理,支持原作者请前往 ...

  7. Java语法

    java语法: 一个java程序可以说是一系列对象的集合,而这些对象都要通过调用彼此的方法来协同工作. 对象: 对象是一个实例,例如:一只猫,它是一个对象,有状态和行为.它的状态状态有:颜色,名字,品 ...

  8. (转)Java集合框架:HashMap

    来源:朱小厮 链接:http://blog.csdn.net/u013256816/article/details/50912762 Java集合框架概述 Java集合框架无论是在工作.学习.面试中都 ...

  9. Java语法糖1:可变长度参数以及foreach循环原理

    语法糖 接下来几篇文章要开启一个Java语法糖系列,所以首先讲讲什么是语法糖.语法糖是一种几乎每种语言或多或少都提供过的一些方便程序员开发代码的语法,它只是编译器实现的一些小把戏罢了,编译期间以特定的 ...

  10. Java集合框架:HashMap

    转载: Java集合框架:HashMap Java集合框架概述   Java集合框架无论是在工作.学习.面试中都会经常涉及到,相信各位也并不陌生,其强大也不用多说,博主最近翻阅java集合框架的源码以 ...

随机推荐

  1. DFT Architecture

    Design For Test 在实际生产过程中产生的physical defect是导致芯片功能出错的根本原因 如何根据结构产生测试向量呢?主要考虑physical defect physical ...

  2. SpringBoot03:首页国际化

    页面国际化 有的时候,我们的网站会去涉及中英文甚至多语言的切换,这时候我们就需要学习国际化! 1.配置文件编写 首先在resources资源文件下新建一个i18n目录,存放国际化配置文件 新建一个lo ...

  3. airsim+px4无人机仿真平台

    0. 架构图 1. 主机列表 对应的ip地址与选择的系统,根据实际情况进行修改 主机IP 组件 系统 192.168.0.28 mavporxy linux-centos7.6 192.168.0.2 ...

  4. Linux-网络-子网-子网掩码-网关-DNS解析

  5. 百度网盘(百度云)SVIP超级会员共享账号每日更新(2023.11.23)

    一.百度网盘SVIP超级会员共享账号 可能很多人不懂这个共享账号是什么意思,小编在这里给大家做一下解答. 我们多知道百度网盘很大的用处就是类似U盘,不同的人把文件上传到百度网盘,别人可以直接下载,避免 ...

  6. [转帖]MySQL如何在InnoDB中重建索引并更新统计数据?

    https://geek-docs.com/mysql/mysql-ask-answer/356_mysql_how_can_i_rebuild_indexes_and_update_stats_in ...

  7. [转帖]Linux命令(64)——strings命令

    https://cloud.tencent.com/developer/article/1414999 1.命令简介 strings命令是二进制工具集GNU Binutils的一员,用于打印文件中可打 ...

  8. 关于cockpit的学习

    关于cockpit的学习 背景 使用node-exporter 可以监控很多资源使用情况 但是这个需要搭建一套prometheus和grafana的工具 并且每个机器都需要安装一套node-expor ...

  9. 【转帖】【奇技淫巧】Linux | 安全保障防火墙-iptables

    虽然说Linux在安全方面确实相当于windows要更加可靠一些,但一般使用其作为服务器的我们,也不能大意,也是需要严格限制网络传输过程中的出入规则.上篇文章我们有聊到统计网络的信息,这篇文章来学习一 ...

  10. [转帖]漏洞预警|Apache Tomcat 信息泄露漏洞

    http://www.hackdig.com/03/hack-953615.htm 棱镜七彩安全预警 近日网上有关于开源项目Apache Tomcat 信息泄露漏洞,棱镜七彩威胁情报团队第一时间探测到 ...