散列表的数据结构以及对象在JVM堆中的存储过程
【版权声明】未经博主同意,谢绝转载!(请尊重原创,博主保留追究权)
https://blog.csdn.net/m0_69908381/article/details/129916399
出自【进步*于辰的博客】
参考笔记二,P67、P68.1。
文章目录
1、什么是“散列表”?
大家先看张图,这是我理解的“散列表”底层数据结构图。

我先大致说说 JVM 的内存结构。
JVM内存结构主要由堆、栈和方法区组成。栈主要用于存储基本数据类型变量和引用、以及引用类型变量引用等;堆主要用于存储对象和数组;方法区主要用于存储类信息和常量。
这里主要讲的是“堆”。
对象在堆中不是随意存储的,而是通过一种数据结构
散列表
\color{green}{散列表}
散列表进行存储(存储于散列表中)。何为散列表?它的数据结构大致如上图所示,纵向是数组,横向是链表(有些资料中也将“链表”称之为
“桶”
\color{green}{“桶”}
“桶”),数组的每一个节点都是链表的表头。
Hash系列(如:java.util.Hashtable<K,V>、java.util.HashMap<K,V>)的数据结构就是散列表,下文以 Hashtable 为例。
P
S
PS
PS:散列表的结构为何这样设计?又为何要将对象采用散列表结构进行存储?往下看。
2、关于对象存储过程
2.1 加载过程
关于创建对象方法,可参考博文《Java知识点锦集》的第3.2项。
对象创建完成,为对象在 Hashtable 中选择一个存储位置,需要使用对象的内存地址在 Hashtable 中进行检索。
- 第一步:将对象内存地址(十六进制)转化成十进制,再通过
h
a
s
h
算法
hash 算法
hash算法,得到一个整型数字,即
hashcode; - 第二步:通过某种运算,得出与此对象具有相同
hashcode的“桶”的位置(索引);(关于“运算”,见本文末) - 第三步:在确定“桶”后,检索,比较
entry中的key,找到对象合适的存储位置后插入“桶”。
补充说明:
\color{purple}{补充说明:}
补充说明:
Hashtable 是一种数据结构,可以存储任何类型数据,对象只是其一。
如果 Hashtable 存储的是对象,则key存储的是内存地址,value存储的是对象。
若 Hashtable 中存储的是对象,则加载过程就是检索对象应存储位置后直接插入对象,不存在覆盖现象。
如:Hashtable<String, Object>,映射(有些资料也称之为
“条目”
\color{green}{“条目”}
“条目”)是Entry<String, Object>。由于key可能重复,故可能会覆盖。
扩展一点:
\color{brown}{扩展一点:}
扩展一点:
大家可能会疑惑:使用Map<String, Object>时,打印key,显示的是 String,并不是你说的内存地址啊?
因为,如果key的类型是 String,如:映射Map.Entry = {"name", [对象]},其底层(JVM中)是先在方法区的字符串常量池中创建一个字符串常量"name",此常量对应一个内存地址,然后将此内存地址经过上文中的“加载过程”作为key存储进Map中。当我们输出key时,在底层会通过其存储的内存地址,去字符串常量池中查找,从而输出"name"。
2.2 注意事项
- h
a
s
h
c
o
d
e
\color{green}{hashcode}
hashcode的作用主要是用于在数组中,定位与对象具有相同
hashcode的“桶”的表头位置(索引),hashcode本身并不存储于 Hashtable。 - 散列表的纵向(数组)和横向(链表)的每个节点的数据类型都是
entry。
注:Hashtable 类中节点的数据类型是Map.Entry。 - 两个不同对象的
hashcode可能相同,同一个“桶”中的所有对象的hashcode都相同。 - 散列表的纵向设计为数组是因为数组便于检索(定位具有相同
hashcode的“桶”效率高),横向设计为链表是因为链表便于修改(插入、修改效率高)。 - “桶”是双向链表
\color{red}{“桶”是双向链表}
“桶”是双向链表.。两头都可以是表头,这样设计是为了提高检索对象的速度。
- “桶”的数据结构不是一成不变的。当
entry个数达到一定临界值时,“桶”会重构,从而提高检索对象性能。
重构规则:
\color{blue}{重构规则:}
重构规则:当
entry个数超过8个时,重构为红黑树
\color{green}{红黑树}
红黑树(大家以二叉树的结构理解就行);当小于等于
6时,重构回链表。
3、Hashtable 扩容机制
先说定义:
“扩容”指扩大 Hashtable 的容量(即“桶”的个数),上面第6点中提及的“重构”指修改“桶”的数据结构,两者不要混淆。
3.1 扩容机制是什么?
Hashtable 何时扩容?这里涉及一个概念——
加载因子
\color{green}{加载因子}
加载因子(又名“负载因子”)。
- 每个“桶”都有一个初始
entry容纳个数。 - “加载因子”指填满(“桶”中
entry个数达到初始容纳个数)的“桶”的个数占 Hashtable 当前容量(指 Hashtable 的当前“桶”个数)的比例,也称之为“扩容阈值”
\color{blue}{“扩容阈值”}
“扩容阈值”。
- 加载因子越大,
h
a
s
h
碰撞
\color{red}{hash碰撞}
hash碰撞越多,性能越低。加载因子的默认值是
0.75,这是考虑到空间(内存)和时间(性能)的折中值。
注:“hash碰撞”就是上文“加载过程”中的第二步。查找与新对象具有相同hashcode的对象所在“桶”的过程。
3.2 扩容时刻
Hashtable 的扩容时刻是:(指当 Hashtable 中entry个数达到的扩容临界值)
x * 0.75 * 8
x是当前容量,默认值为11;0.75是加载因子的默认值;8是“桶”重构的临界值。(注:当entry个数达到8个时,“桶”重构,但仍然可以继续存储,与扩容机制无关)
可实际上,一般情况下,扩容时刻会是:
x * 0.75 * 6
为什么是6,不是8?因为 Hashtable 不存在平均分配的机制,且对象的内存地址是任意的,故 hashcode 任意。
P
S
\color{red}{PS}
PS:这两个“扩容时刻”都是一个估计值,作为理论参考。
补充说明
\color{green}{补充说明}
补充说明:
Hashtable 类有一个属性threshold(扩容阈值),值为当前容量 * 加载因子,此处的“扩容时刻”即threshold与“桶”重构时平均节点数之积。
4、Hashtable 实用举例
@Override
public boolean equals(Object obj) {
if (this.hashCode() == obj.hashCode()) {
if (this == obj)
return true;
else
return false;
} else
return false;
}
使用散列表提高对象检索性能。
5、扩展
Hashtable 与 HashMap 的区别:
- Hashtable 的默认容量为
11,HashMap 的默认容量为16。 - Hashtable 继承于 Dictionary
<K,V>,HashMap 继承于 AbstractMap<K,V>。 - 由于 Hashtable 是线程安全的,故效率低。因此,在多线程环境下,使用ConcurrentHashMap
<K,V>类;而在单线程环境下,使用 HashMap。
最后
相信这篇文章会让大家对散列表的数据结构有了初步的了解,如果大家想进一步了解其底层实现,可查阅这两篇博文:
- Java-API简析_java.util.HashTable<K, V>类(基于 Latest JDK)(浅析源码);
- Java-API简析_java.util.HashMap<K,V>类(基于 Latest JDK)(浅析源码)。
本文完结。
散列表的数据结构以及对象在JVM堆中的存储过程的更多相关文章
- java对象在JVM堆中的数据结构
java对象和数组是存放在堆中的,那么这些instance的数据结构是什么呢? 对象头:对象头存放的是这个对象的一些元数据信息.例如每个对象都有哈希值,GC分代年龄,锁状态标志等,这些信息就是存放在对 ...
- 【阅读笔记:散列表】Javascript任何对象都是一个散列表(hash表)!
什么是散列表? 散列表是Dictionary(字典)的一种散列表实现方式,字典传送门 一个很常见的应用是使用散列表来表示对象.Javascript语言内部就是使用散列表来表示每个对象.此时,对象的每个 ...
- [转载] 散列表(Hash Table)从理论到实用(中)
转载自:白话算法(6) 散列表(Hash Table)从理论到实用(中) 不用链接法,还有别的方法能处理碰撞吗?扪心自问,我不敢问这个问题.链接法如此的自然.直接,以至于我不敢相信还有别的(甚至是更好 ...
- JVM 堆中对象分配、布局和访问
本文摘自深入理解 Java 虚拟机第三版 对象的创建 Java 是一门面向对象的语言,Java 程序运行过程中无时无刻都有对象被创建出来.从语言层面看,创建对象只是一个 new 关键字而已,而在虚拟机 ...
- 白话算法(6) 散列表(Hash Table)从理论到实用(中)
不用链接法,还有别的方法能处理碰撞吗?扪心自问,我不敢问这个问题.链接法如此的自然.直接,以至于我不敢相信还有别的(甚至是更好的)方法.推动科技进步的人,永远是那些敢于问出比外行更天真.更外行的问题, ...
- java中new两个对象,在堆中开辟几个对象空间
内存堆中有两个对象,两个对象里都有独立的变量.p1 p2指向的不是同一个内存空间. 也可以这样描述引用p1,p2指向两个不同的对象.
- 重磅硬核 | 一文聊透对象在 JVM 中的内存布局,以及内存对齐和压缩指针的原理及应用
欢迎关注公众号:bin的技术小屋 大家好,我是bin,又到了每周我们见面的时刻了,我的公众号在1月10号那天发布了第一篇文章<从内核角度看IO模型的演变>,在这篇文章中我们通过图解的方式以 ...
- JavaScript数据结构——字典和散列表的实现
在前一篇文章中,我们介绍了如何在JavaScript中实现集合.字典和集合的主要区别就在于,集合中数据是以[值,值]的形式保存的,我们只关心值本身:而在字典和散列表中数据是以[键,值]的形式保存的,键 ...
- 散列表(Hash table)及其构造
散列表(Hash table) 散列表,是根据关键码值(Key value)而直接进行访问的数据结构.它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度.这个映射函数叫做散列函数,存放记录 ...
- HashMap、lru、散列表
HashMap HashMap的数据结构:HashMap实际上是一个数组和链表("链表散列")的数据结构.底层就是一个数组结构,数组中的每一项又是一个链表. hashCode是一个 ...
随机推荐
- JS Leetcode 263. 丑数 题解分析,来认识有趣的丑数吧
壹 ❀ 引 本题来自LeetCode263. 丑数,难度简单,题目描述如下: 给你一个整数 n ,请你判断 n 是否为 丑数 .如果是,返回 true :否则,返回 false . 丑数 就是只包含质 ...
- NC17872 CSL的校园卡
题目链接 题目 题目描述 今天是阳光明媚,晴空万里的一天,CSL早早就高兴地起床走出寝室到校园里转悠. 但是,等到他回来的时候,发现他的校园卡不见了,于是他需要走遍校园寻找它的校园卡.CSL想要尽快地 ...
- NC14526 购物
题目链接 题目 题目描述 在遥远的东方,有一家糖果专卖店. 这家糖果店将会在每天出售一些糖果,它每天都会生产出 \(m\) 个糖果,第i天的第j个糖果价格为 \(C[i][j]\) 元. 现在的你想要 ...
- Python之凯撒加密
凯撒加密介绍 在密码学中,恺撒密码是一种最简单且最广为人知的加密技术. 它是一种替换加密的技术,明文中的所有字母都在字母表上向后(或向前)按照一个固定数目进行偏移后被替换成密文. 例,当偏移量是3的时 ...
- 《系列二》-- 10、initialize-初始化bean
目录 initializeBean 方法源码如下 二.重要操作 2.1 应用 Aware 2.2 applyBeanPostProcessorsBeforeInitialization: 2.3 in ...
- letcode-Z字抖动
题目 将一个给定字符串 s 根据给定的行数 numRows ,以从上往下.从左到右进行 Z 字形排列. 比如输入字符串为 "PAYPALISHIRING" 行数为 3 时,排列如下 ...
- 一个Git Commit Message模板
一个统一的commit消息模板可以约束团队成员使用一致的方式提交变更信息,这样也方便集成工具进行合规检查. 通常来讲,commit信息应该包含如下内容: <type>(<scope& ...
- OSG开发笔记(二十九):OSG加载模型文件、加载3DMax三维型文件Demo
前言 Osg深入之后需要打开模型文件,这些模型文件是已有的模型文件,加载入osg之后可以在常见中展示模型文件,该节点可以操作,多个逼真的模型的节点就实现了基本的场景构建. Demo ...
- 【LeetCode二叉树#08】寻找树左下角的值(回溯机制X深度)
找树左下角的值 力扣题目链接(opens new window) 给定一个二叉树,在树的最后一行找到最左边的值. 示例 1: 示例 2: 思路 层序遍历 这个是很自然的思路,因为层序遍历可以避免对于& ...
- 【Azure 应用服务】Azure Function 部署槽交换时,一不小心把预生产槽上的配置参数交换到生产槽上,引发生产错误
问题描述 部署Function代码先到预生产槽中,进行测试后通过交换方式,把预生产槽中的代码交换到生产槽上,因为在预生产槽中的设置参数值与生产槽有不同,但是在交换的时候,没有仔细检查.导致在交换的时候 ...