首页 > 其他分享 >散列表的数据结构以及对象在JVM堆中的存储过程

散列表的数据结构以及对象在JVM堆中的存储过程

时间:2024-04-04 10:55:20浏览次数:13  
标签:扩容 存储 对象 列表 hashcode Hashtable JVM 数据结构 堆中

【版权声明】未经博主同意,谢绝转载!(请尊重原创,博主保留追究权)
https://www.cnblogs.com/cnb-yuchen/p/18032068
出自【进步*于辰的博客

参考笔记二,P67、P68.1。

目录

1、什么是“散列表”?

大家先看张图,这是我理解的“散列表”底层数据结构图。
在这里插入图片描述
我先大致说说 JVM 的内存结构。
JVM内存结构主要由堆、栈和方法区组成。栈主要用于存储基本数据类型变量和引用、以及引用类型变量引用等;堆主要用于存储对象和数组;方法区主要用于存储类信息和常量。

这里主要讲的是“堆”。
对象在堆中不是随意存储的,而是通过一种数据结构“散列表”进行存储(存储于散列表中)。何为散列表?它的数据结构大致如上图所示,纵向是数组,横向是链表(有些资料中也将“链表”称之为“”),数组的每一个节点都是链表的表头

Hash系列(如:java.util.Hashtable<K,V>java.util.HashMap<K,V>)的数据结构就是散列表,下文以 Hashtable 为例。

PS:散列表的结构为何这样设计?又为何要将对象采用散列表结构进行存储?往下看。

2、关于对象存储过程

2.1 加载过程

关于创建对象方法,可参考博文《Java知识点锦集》的第3.2项。

对象创建完成,为对象在 Hashtable 中选择一个存储位置,需要使用对象的内存地址在 Hashtable 中进行检索。

  • 第一步:将对象内存地址(十六进制)转化成十进制,再通过$hash 算法$,得到一个整型数字,即hashcode
  • 第二步:通过某种运算,得出与此对象具有相同 hashcode 的“桶”的位置(索引);(关于“运算”,见本文末)
  • 第三步:在确定“桶”后,检索,比较entry中的key,找到对象合适的存储位置后插入“桶”。

补充说明:

Hashtable 是一种数据结构,可以存储任何类型数据,对象只是其一。
如果 Hashtable 存储的是对象,则key存储的是内存地址,value存储的是对象。

若 Hashtable 中存储的是对象,则加载过程就是检索对象应存储位置后直接插入对象,不存在覆盖现象。
如:Hashtable<String, Object>,映射(有些资料也称之为“条目”)是Entry<String, Object>。由于key可能重复,故可能会覆盖。

扩展一点:大家可能会疑惑:使用Map<String, Object>时,打印key,显示的是 String,并不是你说的内存地址啊?
因为,如果key的类型是 String,如:映射Map.Entry = {"name", [对象]},其底层(JVM中)是先在方法区的字符串常量池中创建一个字符串常量"name",此常量对应一个内存地址,然后将此内存地址经过上文中的“加载过程”作为key存储进Map中。当我们输出key时,在底层会通过其存储的内存地址,去字符串常量池中查找,从而输出"name"

2.2 注意事项

  1. hashcode的作用主要是用于在数组中,定位与对象具有相同 hashcode 的“桶”的表头位置(索引), hashcode 本身并不存储于 Hashtable。
  2. 散列表的纵向(数组)和横向(链表)的每个节点的数据类型都是entry
    注:Hashtable 类中节点的数据类型是Map.Entry
  3. 两个不同对象的hashcode可能相同,同一个“桶”中的所有对象的hashcode都相同。
  4. 散列表的纵向设计为数组是因为数组便于检索(定位具有相同hashcode的“桶”效率高),横向设计为链表是因为链表便于修改(插入、修改效率高)。
  5. “桶”是双向链表.。两头都可以是表头,这样设计是为了提高检索对象的速度。
  6. “桶”的数据结构不是一成不变的。当entry个数达到一定临界值时,“桶”会重构,从而提高检索对象性能。
    重构规则:当entry个数超过8个时,重构为红黑树(大家以二叉树的结构理解就行);当小于等于6时,重构回链表。

3、Hashtable 扩容机制

先说定义:

“扩容”指扩大 Hashtable 的容量(即“桶”的个数),上面第6点中提及的“重构”指修改“桶”的数据结构,两者不要混淆。

3.1 扩容机制是什么?

Hashtable 何时扩容?这里涉及一个概念——加载因子(又名“负载因子”)。

  • 每个“桶”都有一个初始entry容纳个数。
  • “加载因子”指填满(“桶”中entry个数达到初始容纳个数)的“桶”的个数占 Hashtable 当前容量(指 Hashtable 的当前“桶”个数)的比例,也称之为“扩容阈值”。
  • 加载因子越大,hash碰撞越多,性能越低。加载因子的默认值是0.75,这是考虑到空间(内存)和时间(性能)的折中值。
    注:“hash碰撞”就是上文“加载过程”中的第二步。查找与新对象具有相同hashcode的对象所在“桶”的过程。

3.2 扩容时刻

Hashtable 的扩容时刻是:(指当 Hashtable 中entry个数达到的扩容临界值)

x * 0.75 * 8

x是当前容量,默认值为110.75是加载因子的默认值;8是“桶”重构的临界值。(注:当entry个数达到8个时,“桶”重构,但仍然可以继续存储,与扩容机制无关)

可实际上,一般情况下,扩容时刻会是:

x * 0.75 * 6

为什么是6,不是8?因为 Hashtable 不存在平均分配的机制,且对象的内存地址是任意的,故 hashcode 任意。

PS:这两个“扩容时刻”都是一个估计值,作为理论参考。

补充说明: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 的区别:

  1. Hashtable 的默认容量为11,HashMap 的默认容量为16
  2. Hashtable 继承于 Dictionary<K,V>,HashMap 继承于 AbstractMap<K,V>
  3. 由于 Hashtable 是线程安全的,故效率低。因此,在多线程环境下,使用ConcurrentHashMap<K,V>类;而在单线程环境下,使用 HashMap。

最后

相信这篇文章会让大家对散列表的数据结构有了初步的了解,如果大家想进一步了解其底层实现,可查阅HashtableHashMap这两个类的源码解析。

本文完结。

标签:扩容,存储,对象,列表,hashcode,Hashtable,JVM,数据结构,堆中
From: https://www.cnblogs.com/cnb-yuchen/p/18032068

相关文章

  • 数据结构——线段树
    本来是想先写树状数组的,但想到要在树状数组里面用到这篇文章,就先写这个了。线段树的思想线段树是在用一颗树存一个数组中的所有数以及他们所有的2的次方数长度的区间,具体样子如下:                      [1~8]     ......
  • JS Graph (图-数据结构)
    Code:/***基于邻接表实现的无向图*@class*/classGraphAdjList{/***@type{Map<any,any[]>}*/adjList;/***构造函数*@constructor*@param{[any,any][]}edges*/constructor(edges){this.a......
  • [转帖]JVM 内存分析工具 MAT 的深度讲解与实践——入门篇
    https://juejin.cn/post/6908665391136899079  注:本文原创,转发需全文转载并标明原文链接。JVM内存分析往往由团队较资深的同学来做,本系列通过3篇文章,深度解析并帮助读者全面深度掌握MAT的使用方法。即使没有JVM内存分析的实践经验,也能快速成为内存分析高手!本系......
  • 数据结构与算法
    1.1数据结构的研究内容程序=数据结构+算法  ———程序的本质 例1:图书管理系统操作对象:若干行数据记录操作算法:查询、插入、删除、修改等操作对象之间的关系:线性关系数据结构:线性数据结构、线性表例2:文件系统的结构图如图可以看到,这是一个典型的树型结构问题,数据......
  • 数据结构——从入门到入土
    链表的建立及遍历:分为如下几步:声明链表这种结构,比如:点击查看代码typedefstructnode*listlink;//定义一个指针类型名称,使指针变量能像其他变量那样声明,而不需要在每个指针变量前加*typedefstructnude(){intdata;structlistlinknext;}LNode;//声明......
  • 数据结构与算法分析实验3 [进阶]通过链表实现多项式加法和乘法
    文章目录大致内容介绍多项式加法代码一览头文件Poly.h内容如下:实现文件Poly.cpp内容如下:初始化增加元素删除元素功能函数遍历函数清除销毁打印多项式向多项式内插入一个元素源文件main.cpp内容如下:实现效果:多项式乘法实现方法:在Poly.h中添加声明:在Poly.cpp中添加实现:在......
  • 数据结构——队列(包括循环队列)——Java版
    目录队列介绍:基本概念:应用:Java实现示例:循环队列的Java实现:队列介绍:队列(Queue)是一种常见的数据结构,它按照先进先出(FIFO,First-In-First-Out)的原则管理数据。在现实生活中,队列的概念很容易理解,就像是排队等待服务的人群一样。在计算机科学领域,队列同样扮演着重要的角色,在......
  • MySQL索引背后的数据结构及算法原理
    摘要本文以MySQL数据库为研究对象,讨论与数据库索引相关的一些话题。特别需要说明的是,MySQL支持诸多存储引擎,而各种存储引擎对索引的支持也各不相同,因此MySQL数据库支持多种索引类型,如BTree索引,哈希索引,全文索引等等。为了避免混乱,本文将只关注于BTree索引,因为这是平常使用MyS......
  • python常见数据结构及方法
    Python提供了多种内置的数据结构,这些数据结构非常灵活且功能强大,能够满足各种程序设计需求。下面是一些最常用的Python数据结构及其内置方法的详细说明:1.列表(List)列表是Python中最基本的数据结构之一。列表可以包含不同类型的元素,包括另一个列表。常用内置方法:append(x......
  • 【数据结构与算法篇】动态顺序表实战项目:通讯录
    【数据结构与算法】动态顺序表实战项目:通讯录......