我的编程空间,编程开发者的网络收藏夹
学习永远不晚

如何用源码分析Java HashMap实例

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

如何用源码分析Java HashMap实例

本篇文章为大家展示了如何用源码分析Java HashMap实例,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。

引言

HashMap在键值对存储中被经常使用,那么它到底是如何实现键值存储的呢?

一 Entry

Entry是Map接口中的一个内部接口,它是实现键值对存储关键。在HashMap中,有Entry的实现类,叫做Entry。Entry类很简 单,里面包含key,value,由外部引入的hash,还有指向下一个Entry对象的引用,和数据结构中学的链表中的note节点很类似。

Entry类的属性和构造函数:

final K key; V value; Entry<K,V> next; int hash;  Entry(int h, K k, V v, Entry<K,V> n) { value = v; next = n; key = k; hash = h; }

二 HashMap的初始化

//HashMap构造方法 public HashMap(int initialCapacity, float loadFactor) { if (initialCapacity < 0)   throw new IllegalArgumentException("Illegal initial capacity: " +              initialCapacity); if (initialCapacity > MAXIMUM_CAPACITY)   initialCapacity = MAXIMUM_CAPACITY; if (loadFactor <= 0 || Float.isNaN(loadFactor))   throw new IllegalArgumentException("Illegal load factor: " +              loadFactor); this.loadFactor = loadFactor; threshold = initialCapacity; init(); }

这是HashMap的构造函数之一,其他构造函数都引用这个构造函数进行初始化。参数InitialCapacity指的是HashMap中 table数组最初的大小,参数loadFactory指的是HashMap可容纳键值对与数组长度的比值(举个例子:数组长度默认值为 16,loadFactory默认值为0.75,如果HashMap中存储的键值对即Entry多于12,则会进行扩容,扩容后大小为当前数组长度的2 倍)。在构造函数中不会对数组进行初始化,只有在put等操作方法内会进行判断是否要初始化或扩容。

三 table数组

在HashMap中有一个概念叫做threshold(实际可容纳量),实际可容纳量指的是在HashMap中允许存在最多的Entry的个数,它 是由HashMap中内置的数组table的长度*load factory(负载因子)得来。其作用是保证HashMap的效率。

table数组是HashMap实现键值对存储的又一关键,具体键值对是怎么存的呢?请看下图

如何用源码分析Java HashMap实例

如图中的[key,value]就是Entry对象来实现的,而table数组是用来存放Entry对象的。

//数组的初始化:

private static int roundUpToPowerOf2(int number) { return number >= MAXIMUM_CAPACITY    ? MAXIMUM_CAPACITY    : (number > 1) ? Integer.highestOneBit((number - 1) << 1) : 1; } private void inflateTable(int toSize) { // Find a power of 2 >= toSize int capacity = roundUpToPowerOf2(toSize); threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1); table = new Entry[capacity]; initHashSeedAsNeeded(capacity); }

在put等方法中发现数组未进行初始化时会调用InflateTable方法进行初始化,输入参数为初始设置的InitialCapacity,实 际上他会调用roundUpToPowerOf2方法返回一个比初始容量大的最小的2的幂数(其中一个原因是在得到Entry所在数组位置时方便)。

四 put方法

public V put(K key, V value) { if (table == EMPTY_TABLE) {   inflateTable(threshold); } if (key == null)   return putForNullKey(value); int hash = hash(key); int i = indexFor(hash, table.length); for (Entry<K,V> e = table[i]; e != null; e = e.next) {   Object k;   if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {    V oldValue = e.value;    e.value = value;    e.recordAccess(this);    return oldValue;   } } modCount++; addEntry(hash, key, value, i); return null; } private V putForNullKey(V value) { for (Entry<K,V> e = table[0]; e != null; e = e.next) {   if (e.key == null) {    V oldValue = e.value;    e.value = value;    e.recordAccess(this);    return oldValue;   } } modCount++; addEntry(0, null, value, 0); return null; } void addEntry(int hash, K key, V value, int bucketIndex) { if ((size >= threshold) && (null != table[bucketIndex])) {   resize(2 * table.length);   hash = (null != key) ? hash(key) : 0;   bucketIndex = indexFor(hash, table.length); } createEntry(hash, key, value, bucketIndex); } void createEntry(int hash, K key, V value, int bucketIndex) { Entry<K,V> e = table[bucketIndex]; table[bucketIndex] = new Entry<>(hash, key, value, e); size++; }

在put方法中

首先会判断数组是否为空,如果为空会对数组进行初始化。

接下来判断key是否为null,如果为null就采用第二个方法对键值对进行put。

接下来对key进行hash得到一个数值,再对这个数值进行处理(IndexFor方法)得到所在数组中的位置。

接下来会遍历所在数组位置的链表,如果key的hash和传入key的hash相同且(key内存地址相等 或 equals方法相等),则意味着会更新在链表中的value值,并返回旧的value值。

如果上边的方法都没有奏效,则会调用第三个方法,创建一个新的Entry对象。

在putForNullKey方法中 ,我们看到它是为了NULL值专门设置的,NULL值的hash始终为0,所以key为NULL的Entry对象肯定在数组的第0个位置。同样,如果找到则更新,没有找到则添加。

调用addEntry方法  意味着要往这个数组链表中添加一个Entry,所以会在最开始判断已经存在的Entry数量是否超过了实际可容纳量。如果超过了,则会调用resize方 法将数组扩大两倍,注意在扩大之后会对已经存入的Entry进行重排,原因是当初存入时IndexFor方法与数组长度有关系。接着会调用第四个方法。

createEntry方法 很简单,就是将原本在数组中存放的链表头置入到新的Entry之后,将新的Entry放入数组中。从这里我们可以看出HashMap不保证顺序问题。

get方法和contains方法原理和put方法一致,即先通过对key的hash得到其value值所在的链表头在数组中的位置,再通过equals方法判断value是否存在。

五 其他

//hash方法 final int hash(Object k) { int h = hashSeed; if (0 != h && k instanceof String) {   return sun.misc.Hashing.stringHash42((String) k); } h ^= k.hashCode(); // This function ensures that hashCodes that differ only by // constant multiples at each bit position have a bounded // number of collisions (approximately 8 at default load factor). h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4); }

hash方法中最终返回值与key的hashCode方法有关。

总结

  1. 最终数组初始化的容量大小会是大于等于你传入初始容量的最小2的幂数。

  2. key为null或value为null能存入HashMap的原因是对null值会进行单独的操作。

  3. 在table数组中的链表中每个Entry的共同点是key的hash(key.hashCode)部分相同。

  4. 注意对key的hashCode和equals方法的重写当你想让两个key映射一个对象,因为判定key相等的条件是(hashCode相等+(内存相等 或 equals相等))。

  5. 最早存入的键值对会在链表的末端。

  6. 当数组没有链表存在时,HashMap性能***为O(1)。而最差为O(threshould)。

上述内容就是如何用源码分析Java HashMap实例,你们学到知识或技能了吗?如果还想学到更多技能或者丰富自己的知识储备,欢迎关注编程网行业资讯频道。

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

如何用源码分析Java HashMap实例

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

如何用源码分析Java HashMap实例

本篇文章为大家展示了如何用源码分析Java HashMap实例,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。引言HashMap在键值对存储中被经常使用,那么它到底是如何实现键值存储的呢?一 Entr
2023-06-17

Java HashMap使用源码分析

这篇文章主要讲解了“Java HashMap使用源码分析”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Java HashMap使用源码分析”吧!0. 成员变量首先我们先看一下 HashMap
2023-07-06

Java源码角度分析HashMap用法

—HashMap—优点:超级快速的查询速度,时间复杂度可以达到O(1)的数据结构非HashMap莫属。动态的可变长存储数据(相对于数组而言)。缺点:需要额外计算一次hash值,如果处理不当会占用额外的空间。—HashMap如何使用—平时我们
2023-05-30

如何进行HashMap扩容机制源码分析

这期内容当中小编将会给大家带来有关如何进行HashMap扩容机制源码分析,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。具体看源码之前,我们先简单的说一下HashMap的底层数据结构  1、HashMap底
2023-06-02

Java数据结构之HashMap源码深入分析

JavaHashMap是一种基于哈希表实现的键值对存储结构,可以实现快速的数据查找和存储。它是线程不安全的,但在单线程环境中运行效率高,被广泛应用于Java开发中
2023-05-17

Java数据结构之HashMap和HashSet源码分析

本篇内容介绍了“Java数据结构之HashMap和HashSet源码分析”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!1、认识 HashMa
2023-07-05

如何解读Java HashMap核心源码

这期内容当中小编将会给大家带来有关如何解读Java HashMap核心源码,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。对HashMap实现的源码进行简单的分析。 所使用的HashMap源码的版本信息如下
2023-06-17

Java源码ConcurrentHashMap的示例分析

小编给大家分享一下Java源码ConcurrentHashMap的示例分析,希望大家阅读完这篇文章之后都有所收获,下面让我们一起去探讨吧!一、记录形式打算直接把过程写在源码中,会按序进行注释,查阅的时候可以按序号只看注释部分二、Concur
2023-06-15

如何进行Netlink源码及实例的分析

本篇文章给大家分享的是有关如何进行Netlink源码及实例的分析,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。前言这几天在看 ipvs 相关代码的时候又遇到了 netlink
2023-06-15

不容错过的HashMap实现原理及源码分析

哈希表(hash table)也叫散列表,是一种非常重要的数据结构,应用场景及其丰富,许多缓存技术(比如memcached)的核心其实就是在内存中维护一张大的哈希表,而HashMap的实现原理也常常出现在各类的面试题中,重要性可见一斑。本文
2023-06-02

Java中Handler源码的示例分析

这篇文章主要介绍了Java中Handler源码的示例分析,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。从很早开始就认识到 Handler 了,只不过那时修为尚浅,了解的不够深
2023-06-02

java中CopyOnWriteArrayList源码的示例分析

这篇文章将为大家详细讲解有关java中CopyOnWriteArrayList源码的示例分析,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。简介CopyOnWriteArrayList是ArrayList的
2023-06-29

java LRU算法及Apache LRUMap源码实例分析

本篇内容主要讲解“java LRU算法及Apache LRUMap源码实例分析”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“java LRU算法及Apache LRUMap源码实例分析”吧!1.
2023-06-21

Vue3 源码分析reactive readonly实例

这篇文章主要为大家介绍了Vue3 源码分析reactive readonly实例,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2022-11-13

Java源码解析之ConcurrentHashMap的示例分析

小编给大家分享一下Java源码解析之ConcurrentHashMap的示例分析,希望大家阅读完这篇文章之后都有所收获,下面让我们一起去探讨吧!早期 ConcurrentHashMap,其实现是基于:分离锁,也就是将内部进行分段(Segme
2023-06-15

java代码实例分析

这篇文章主要介绍“java代码实例分析”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“java代码实例分析”文章能帮助大家解决问题。一、几个坑爹代码的目录1、这样使用 StringBuffer 的方法
2023-06-16

Java SpringBoot核心源码的示例分析

本篇文章给大家分享的是有关Java SpringBoot核心源码的示例分析,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。SpringBoot源码主线分析我们要分析一个框架的源码
2023-06-22

如何用源代码分析FileZilla

这期内容当中小编将会给大家带来有关如何用源代码分析FileZilla,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。FileZilla是一种快速、可信赖的FTP客户端以及服务器端开放源代码程式,具有多种特色
2023-06-16

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录