热门标签 | HotTags
当前位置:  开发笔记 > Android > 正文

Java中map内部存储方式解析

这篇文章主要介绍了Java中map内部存储方式解析的相关内容,涉及其实现方式,以及对存储方式作了简单的比较,具有一定参考价值,需要的朋友可了解下。

Map,即映射,也称为 键值对,有一个 Key, 一个 Value 。

比如 Groovy 语言中,  def  map = ['name' : 'liudehua', 'age' : 50 ] ,则 map[ 'name' ]  的值是 'liudehua'。
那么 Map 内部存储是怎么实现的呢?   下面慢慢讲解.

一、 使用 拉链式存储

这个以 Java 中的 HashMap 为例进行讲解。   HashMap 的内部有个数组 Entry[]  table, 这个数组就是存放数据的。

Entry 类的定义大致是 :

class Entry {
Object key
Object value
Entry next;
}

所以, Entry[]  table  的每个元素都是一个链表,即 HashMap 的内部存储是 数组 + 链表,即拉链式存储。

当往 HaspMap 中 put(key,  value) 数据时,先进行  key.hashCode()  &  (table.length() - 1) ,得到一个小于 table.length() 的值, 称为 index, 则这个新的 Entry 就属于 table[index] 这个链表了 ( 如果链表还不存在,则把这个新的 Entry 作为链表的头部了 );  然后开始从前往后遍历  table[index] 这个链表,如果 key.equals( entry.key ), 那么表示这个 key 已经有了旧值,则替换 value 的值即可;

否则,把这个新的 Entry 插入到 table[index] 链表的最前面.

以上就是 HashMap 的存储方式介绍了, 而且可以知道,作为 HashMap 的 Key, 它的 hashCode() 和 equals() 方法都被使用了

二、数组存储

1.分散的数组存储

这个以 ThreadLocal  和 ThreadLocal.Values  类为例进行讲解。 Values 类里面有两个变量, Object[]  table, 和 mask , table 就是存储数据的数组了,table 的长度是 2 的倍数 ,  mask 的值就是  table.length - 1 ;  这一点和 HashMap 的内部存储很像。  不过 table 中的每个元素就不是链表了。

当往  Values 中进行 put(key, value) 时,先进行 key.hashCode & mask ,得到一个小于 table.length 的值,称为 index (与上面的 HashMap 好像,哈哈), 然后去检查 table[index] 的值,如果不存在,则在 table[index] 处放入 key, table[index + 1] 处放入 value; 如果已经存在了,且 key.equals( oldKey ) 不成立,即发生了冲突,那么 index = index + 2 ( 此处是 + 2,因为 key ,value 两个是挨着放的,一个元素占俩坑 ) ; 往下一地方找找,如果再冲突,再找,直到找到一个可插入的地方,把 table[index] = key, table[index + 1] = value; 

有两处需要注意:

key.hashCode() 必须是 2 的倍数, 否则 index 的值有可能为奇数,如此就可能发生冲突了.  可参考 ThreadLocal.hash 这个成员变量

table 内部的数据是分散着存储的.

2.连续的数组存储

这个以 Android 中新增的 ArrayMap 为例进行分析( 据说没 ArrayMap 是用来替换 HashMap 的哈 ),  ArrayMap 中有两个主要变量, int[]  mHashes, Object[]  mArrays.

mHashes 主要是存放 key 的 hash 值的, mArrays 是用来存放数据的,它也是奇数存放 key ,偶数存放 value , key 和 value 顺序排列( 这个和 TheadLocal.value 中的 table 存储方式很像 )。  mArrays 的长度是 mHashes 的 2 倍,毕竟 mArrays 是 key, value 都要存嘛~

mHashes 中存放 key 的 hash 值,是从小到大排列的,如果有多个 key 的 hash 值有一样的,那么就挨着排列

当往 ArrayMap 中进行 put(key, value) 时,先 int hash = key.hashCode, 然后通过二分查找在 mHashes 中查找 hash 的位置( 如果里面有,就返回,如果无,就先找到最接近的位置,然后进行取反操作并返回 )  index,如果 index > 0 ,那么再去 mArrays 中 2 * index 处获取 key 值,比对两个 key 是否 equals(), 如果不相等,那么再分别向上、向下查找附近相同 hash 值的 key ,看是否有 equals() 的 key, 若有,则替换,若无,则插入;    如果 index <0 ,表示之前没有相等 hash 的 key 插入过,那么 index = ~index( 再次取反,就是个正数了,代办要插入的位置), 再在 mHashes 的 index 处插入 hash, 在 mArrays 的 2 * index 处插入 key, 在 (2 * index ) + 1 处,插入 value .

注意:

mHashes 和 mArrays 在插入新数据时,都需要把插入位置后面的数据向后移动一个单位,这个对于频繁插入、删除的动作来说消耗比较大.

key 的 hash 大小决定了插入的顺序

3.以数字为 key 的数组存储

这类的 map 比较特殊,key 是数字类型。  这个以 Android 中新增的 SparseArray 进行分析。 SparseArray 中有两个主要变量, int[]  mKeys  和  Object[]  mValues , mKeys 是存放 key 的,是个有序数组,从小到大排列;  mValues 是与 mKeys 一一对应的 value 值集合.   mKeys 和 mValues 的长度是相等的。

当往  SparseArray 中 put(key, value) 时,先用二分查找在 mKeys 中查找 key 所在的位置 (如果找到,返回; 如果没有找到,则找到它应该插入的位置,取反并返回) ,记为 index, index > 0 ,则直接在 mValues[index] 处替换 value; 如果 index <0 ,则 index = ~index, 即取反, 然后在 mKeys 的 index 处插入 key , 在 mValues[index] 处插入 value ,之前的数据自 index 处后移一个单位。

注意:

mKeys 和 mArrays 的数据插入时,都是要进行数据移动的,对频繁插入、删除的 map 来说消耗很大.
最后了,对它们的优缺点做些对比。

HashMap : 内存占用较大,增、删的效率较高,改、查的效率一般

ThreadLocal.Values :  内存占用一般,当数据量比较小时,增删改查的效率高;数据量大时,增删改查效率一般

ArrayMap: 内存占用较小,改、查的效率高,增、删的效率较低

SparseArray : 内存占用较小,改、查的效率高,增、删的效率低,且主键是数字

总结

我们不评判哪种存储方式好,一切都要以实际情况实际分析,找出最符合的那种存储,哈哈~。希望对大家有所帮助。感兴趣的朋友可以参阅:Javabean和map相互转化方法代码示例  浅谈对象与Map相互转化  Struts2 使用OGNL遍历map方法详解等。感谢大家对本站的支持。


推荐阅读
  • 国内BI工具迎战国际巨头Tableau,稳步崛起
    尽管商业智能(BI)工具在中国的普及程度尚不及国际市场,但近年来,随着本土企业的持续创新和市场推广,国内主流BI工具正逐渐崭露头角。面对国际品牌如Tableau的强大竞争,国内BI工具通过不断优化产品和技术,赢得了越来越多用户的认可。 ... [详细]
  • 优化ListView性能
    本文深入探讨了如何通过多种技术手段优化ListView的性能,包括视图复用、ViewHolder模式、分批加载数据、图片优化及内存管理等。这些方法能够显著提升应用的响应速度和用户体验。 ... [详细]
  • 本文将详细介绍如何使用剪映应用中的镜像功能,帮助用户轻松实现视频的镜像效果。通过简单的步骤,您可以快速掌握这一实用技巧。 ... [详细]
  • 深入理解Cookie与Session会话管理
    本文详细介绍了如何通过HTTP响应和请求处理浏览器的Cookie信息,以及如何创建、设置和管理Cookie。同时探讨了会话跟踪技术中的Session机制,解释其原理及应用场景。 ... [详细]
  • 本文介绍如何在 Xcode 中使用快捷键和菜单命令对多行代码进行缩进,包括右缩进和左缩进的具体操作方法。 ... [详细]
  • 本文介绍了一款用于自动化部署 Linux 服务的 Bash 脚本。该脚本不仅涵盖了基本的文件复制和目录创建,还处理了系统服务的配置和启动,确保在多种 Linux 发行版上都能顺利运行。 ... [详细]
  • 在Linux系统中配置并启动ActiveMQ
    本文详细介绍了如何在Linux环境中安装和配置ActiveMQ,包括端口开放及防火墙设置。通过本文,您可以掌握完整的ActiveMQ部署流程,确保其在网络环境中正常运行。 ... [详细]
  • Android 渐变圆环加载控件实现
    本文介绍了如何在 Android 中创建一个自定义的渐变圆环加载控件,该控件已在多个知名应用中使用。我们将详细探讨其工作原理和实现方法。 ... [详细]
  • 如何在WPS Office for Mac中调整Word文档的文字排列方向
    本文将详细介绍如何使用最新版WPS Office for Mac调整Word文档中的文字排列方向。通过这些步骤,用户可以轻松更改文本的水平或垂直排列方式,以满足不同的排版需求。 ... [详细]
  • 本文总结了在使用Ionic 5进行Android平台APK打包时遇到的问题,特别是针对QRScanner插件的改造。通过详细分析和提供具体的解决方法,帮助开发者顺利打包并优化应用性能。 ... [详细]
  • 理解存储器的层次结构有助于程序员优化程序性能,通过合理安排数据在不同层级的存储位置,提升CPU的数据访问速度。本文详细探讨了静态随机访问存储器(SRAM)和动态随机访问存储器(DRAM)的工作原理及其应用场景,并介绍了存储器模块中的数据存取过程及局部性原理。 ... [详细]
  • 360SRC安全应急响应:从漏洞提交到修复的全过程
    本文详细介绍了360SRC平台处理一起关键安全事件的过程,涵盖从漏洞提交、验证、排查到最终修复的各个环节。通过这一案例,展示了360在安全应急响应方面的专业能力和严谨态度。 ... [详细]
  • 几何画板展示电场线与等势面的交互关系
    几何画板是一款功能强大的物理教学软件,具备丰富的绘图和度量工具。它不仅能够模拟物理实验过程,还能通过定量分析揭示物理现象背后的规律,尤其适用于难以在实际实验中展示的内容。本文将介绍如何使用几何画板演示电场线与等势面之间的关系。 ... [详细]
  • 本文介绍如何通过Windows批处理脚本定期检查并重启Java应用程序,确保其持续稳定运行。脚本每30分钟检查一次,并在需要时重启Java程序。同时,它会将任务结果发送到Redis。 ... [详细]
  • MySQL中枚举类型的所有可能值获取方法
    本文介绍了一种在MySQL数据库中查询枚举(ENUM)类型字段所有可能取值的方法,帮助开发者更好地理解和利用这一数据类型。 ... [详细]
author-avatar
手机用户2502940777
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有