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

HashMap实现快速存取的原理

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

北京

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

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

看不清楚,换张图片

免费获取短信验证码

HashMap实现快速存取的原理

一、存储实现:put(key,vlaue)

      首先我们先看源码: 

// 将“key-value”添加到HashMap中  
public V put(K key, V value) {  
    // 若“key为null”,则将该键值对添加到table[0]中。  
    if (key == null)  
        return putForNullKey(value);  
    // 若“key不为null”,则计算该key的哈希值,然后将其添加到该哈希值对应的链表中。  
    int hash = hash(key.hashCode());   // 计算key hash值在table数组中的位置  ------------ (1)
    int i = indexFor(hash, table.length);  // 迭代e,从i处开始,找到key保存的位置  ------------ (2)
    for (Entry<K,V> e = table[i]; e != null; e = e.next) {  
        Object k;  
        // 若“该key”对应的键值对已经存在,则用新的value取代旧的value。然后退出!  
        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {  
            V oldValue = e.value;  
            e.value = value;  
            e.recordAccess(this);  
            return oldValue;  
        }  
    }  
     // 若“该key”对应的键值对不存在,则将“key-value”添加到table中  
    modCount++;
    //将key-value添加到table[i]处
    addEntry(hash, key, value, i);  
    return null;  
}

      通过源码我们可以清晰看到HashMap保存数据的过程为:首先判断key是否为null,若为null,则直接调用putForNullKey方法。若不为空则先计算key的hash值,然后根据hash值搜索在table数组中的索引位置,如果table数组在该位置处有元素,则通过比较是否存在相同的key,若存在则覆盖原来key的value,否则将该元素保存在链头(最先保存的元素放在链尾)。若table在该处没有元素,则直接保存。
      1、 先看迭代处。此处迭代原因就是为了防止存在相同的key值,若发现两个hash值(key)相同时,HashMap的处理方式是用新value替换旧value,这里并没有处理key,这就解释了HashMap中没有两个相同的key。
      2、 再看(1)、(2)处。这里是HashMap的精华所在。首先是hash方法,该方法为一个纯粹的数学计算,就是计算h的hash值。

static int hash(int h) {  
return useNewHash ? newHash(h) : oldHash(h);  
}

useNewHash声明如下:

private static final boolean useNewHash;  
static { useNewHash = false; }

 

private static int oldHash(int h) {  
    h += ~(h << 9);  
    h ^=  (h >>> 14);  
    h +=  (h << 4);  
    h ^=  (h >>> 10);  
    return h;  
}    
private static int newHash(int h) {  
    // 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);  
}

      我们知道对于HashMap的table而言,数据分布需要均匀(最好每项都只有一个元素,这样就可以直接找到),不能太紧也不能太松,太紧会导致查询速度慢,太松则浪费空间。计算hash值后,怎么才能保证table元素分布均与呢?我们会想到取模,但是由于取模的消耗较大,HashMap是这样处理的:调用indexFor方法。

static int indexFor(int h, int length) {  
return h & (length-1);  
}

      HashMap的底层数组长度总是2的n次方,在构造函数中存在:capacity <<= 1;这样做总是能够保证HashMap的底层数组长度为2的n次方。当length为2的n次方时,h&(length - 1)就相当于对length取模,而且速度比直接取模快得多,这是HashMap在速度上的一个优化。至于为什么是2的n次方下面解释。
      我们回到indexFor方法,该方法仅有一条语句:h&(length - 1)  作用:均匀分布table数据和充分利用空间。
      这里我们假设length为16(2^n)和15,h为5、6、7。

length = 16
hlength - 1h & length -1 
5150101 & 1111 = 001015
6150110 & 1111 = 001106
7150111 & 1111 = 001117
length = 15
5140101 & 1110 = 001015
6140110 & 1110 = 001106
7140111 & 1110 = 001106

当n=15时,6和7的结果一样,这样表示他们在table存储的位置是相同的,也就是产生了碰撞,6、7就会在一个位置形成链表,这样就会导致查询速度降低。诚然这里只分析三个数字不是很多,那么我们就看0-15。

hlength - 1h & length - 1 
0140000 & 1110 = 00000
1140001 & 1110 = 00000
2140010 & 1110 = 00102
3140011 & 1110 = 00102
4140100 & 1110 = 01004
5140101 & 1110 = 01004
6140110 & 1110 = 01106
7140111 & 1110 = 01106
8141000 & 1110 = 10008
9141001 & 1110 = 10008
10141010 & 1110 = 101010
11141011 & 1110 = 101010
12141100 & 1110 = 110012
13141101 & 1110 = 110012
14141110 & 1110 = 111014
15141111 & 1110 = 111014

      从上面的图表中我们看到总共发生了8此碰撞,同时发现浪费的空间非常大,有1、3、5、7、9、11、13、15处没有记录,也就是没有存放数据。这是因为他们在与14进行&运算时,得到的结果最后一位永远都是0,即0001、0011、0101、0111、1001、1011、1101、1111位置处是不可能存储数据的,空间减少,进一步增加碰撞几率,这样就会导致查询速度慢。而当length = 16时,length – 1 = 15 即1111,那么进行低位&运算时,值总是与原来hash值相同,而进行高位运算时,其值等于其低位值。所以说当length = 2^n时,不同的hash值发生碰撞的概率比较小,这样就会使得数据在table数组中分布较均匀,查询速度也较快。
      这里我们再来复习put的流程:当我们想一个HashMap中添加一对key-value时,系统首先会计算key的hash值,然后根据hash值确认在table中存储的位置。若该位置没有元素,则直接插入。否则迭代该处元素链表并依此比较其key的hash值。如果两个hash值相等且key值相等(e.hash == hash && ((k = e.key) == key || key.equals(k))),则用新的Entry的value覆盖原来节点的value。如果两个hash值相等但key值不等 ,则将该节点插入该链表的链头。具体的实现过程见addEntry方法,如下:

void addEntry(int hash, K key, V value, int bucketIndex) {  
    // 获取bucketIndex处的Entry
Entry<K,V> e = table[bucketIndex];  
// 将新创建的 Entry 放入 bucketIndex 索引处,并让新的 Entry 指向原来的 Entry
table[bucketIndex] = new Entry<K,V>(hash, key, value, e);  
    // 若HashMap中元素的个数超过极限了,则容量扩大两倍
if (size++ >= threshold)  
    resize(2 * table.length);  
}

      这个方法中有两点需要注意:
      一、链的产生。
系统总是将新的Entry对象添加到bucketIndex处。如果bucketIndex处已经有了对象,那么新添加的Entry对象将指向原有的Entry对象,形成一条Entry链,但是若bucketIndex处没有Entry对象,也就是e==null,那么新添加的Entry对象指向null,也就不会产生Entry链了。
      二、扩容问题。
      随着HashMap中元素的数量越来越多,发生碰撞的概率就越来越大,所产生的链表长度就会越来越长,这样势必会影响HashMap的速度,为了保证HashMap的效率,系统必须要在某个临界点进行扩容处理。该临界点在当HashMap中元素的数量等于table数组长度*加载因子。但是扩容是一个非常耗时的过程,因为它需要重新计算这些数据在新table数组中的位置并进行复制处理。所以如果我们已经预知HashMap中元素的个数,那么预设元素的个数能够有效的提高HashMap的性能。

二、读取实现:get(key)

      相对于HashMap的存而言,取就显得比较简单了。通过key的hash值找到在table数组中的索引处的Entry,然后返回该key对应的value即可。

// 获取key对应的value  
public V get(Object key) {
// 若为null,调用getForNullKey方法返回相对应的value
    if (key == null)  
     // 根据该 key 的 hashCode 值计算它的 hash 码
        return getForNullKey();  
    // 获取key的hash值  
    int hash = hash(key.hashCode());  
    // 取出 table 数组中指定索引处的值,在“该hash值对应的链表”上查找“键值等于key”的元素  
    for (Entry<K,V> e = table[indexFor(hash, table.length)];  
         e != null;  
         e = e.next) {  
        Object k;  
        //判断key是否相同,若key与查找的key相同,则返回相对应的value
        if (e.hash == hash && ((k = e.key) == key || key.equals(k)))  
            return e.value;  
    }
    //没找到则返回null
    return null;  
}

     

 在这里能够根据key快速的取到value除了和HashMap的数据结构密不可分外,还和Entry有莫大的关系,在前面就提到过,HashMap在存储过程中并没有将key,value分开来存储,而是当做一个整体key-value来处理的,这个整体就是Entry对象。同时value也只相当于key的附属而已。在存储的过程中,系统根据key的hashcode来决定Entry在table数组中的存储位置,在取的过程中同样根据key的hashcode取出相对应的Entry对象。上海尚学堂java培训原创,陆续java技术相关文章奉上,请多关注。

免责声明:

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

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

HashMap实现快速存取的原理

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

下载Word文档

猜你喜欢

HashMap实现快速存取的原理

一、存储实现:put(key,vlaue) 首先我们先看源码: // 将“key-value”添加到HashMap中 public V put(K key, V value) { // 若“key为null”,则将该键
2023-06-03

HashMap的底层实现原理

这篇文章主要介绍“HashMap的底层实现原理”,在日常操作中,相信很多人在HashMap的底层实现原理问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”HashMap的底层实现原理”的疑惑有所帮助!接下来,请跟
2023-06-04

HashMap的底层实现原理是什么

这篇文章给大家介绍HashMap的底层实现原理是什么,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。1.HashMap的常用方法// Hashmap存值:--------------------------------
2023-06-06

基于Java快速实现一个简单版的HashMap详解

这篇文章主要为大家详细介绍了如何利用Java简单实现一个底层数据结构为数组 + 链表的HashMap,不考虑链表长度超过8个时变为红黑树的情况,需要的可以参考一下
2023-02-08

深入浅析java 中HashMap的实现原理

这篇文章将为大家详细讲解有关深入浅析java 中HashMap的实现原理,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。1. HashMap的数据结构数据结构中有数组和链表来实现对数据的存储,
2023-05-31

Java Map揭秘:实现数据快速存取的技巧和策略

Java Map是一个强大的数据结构,它允许开发人员快速存储和检索键值对。本文探讨了如何使用Map来优化应用程序的性能,并提供了使用Map的技巧和策略。
Java Map揭秘:实现数据快速存取的技巧和策略
2024-02-05

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

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

Java中​HashMap的工作原理及实现方法是什么

今天小编给大家分享一下Java中HashMap的工作原理及实现方法是什么的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。Has
2023-06-03

如何快速简单的实现Excel数据按列提取

这期内容当中小编将会给大家带来有关如何快速简单的实现Excel数据按列提取,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。一般常规办法:使用第三方类库(POI或者HSSFWorkbook等)来读取EXCEL
2023-06-03

Sphinx 实现 PHP 项目的快速搜索与缓存优化

一、Sphinx 的安装与配置要使用 Sphinx,首先需要在服务器上进行安装并进行相关的配置。以下是安装 Sphinx 的步骤:下载 Sphinx:在 Sphinx 的官方网站(http://sphinxsearch.com/downlo
2023-10-21

深入探索快速静态定位方法的核心原理和实现方式

深入了解快速静态定位方法的基本原理与实现随着科技的不断进步,定位技术也得到了飞速发展。在现代社会中,人们对精准定位的需求越来越高,涵盖了许多领域,如地理导航、智能交通、无人驾驶等。为了实现高精度、快速的定位,人们提出了各种快速静态定位方法
深入探索快速静态定位方法的核心原理和实现方式
2024-01-18

cdn加速技术的实现原理是什么

CDN加速技术的实现原理是将内容分发到全球各地的服务器节点上,用户访问时就近选择最优的节点获取内容,从而实现加速访问的效果。具体实现原理如下:1. CDN服务商在全球各地建立服务器节点,这些节点分布在不同的地理位置,每个节点都存储着相同的内
2023-06-11

PHP数据结构:散列表的实现原理,探究数据快速查找的奥秘

散列表是一种高效的数据结构,它通过将数据映射到固定大小的数组(“桶”)实现快速查找,每个桶包含具有相同键的数据。php 中的散列表使用哈希函数,将任意大小的数据转换为固定长度的整数,该整数用于计算数据在散列表中的桶。PHP 数据结构:散列表
PHP数据结构:散列表的实现原理,探究数据快速查找的奥秘
2024-05-15

Java和Android的LRU缓存及实现原理

一、概述 Android提供了LRUCache类,可以方便的使用它来实现LRU算法的缓存。Java提供了LinkedHashMap,可以用该类很方便的实现LRU算法,Java的LRULinkedHashMap就是直接继承了LinkedHas
2022-06-06

编程热搜

  • 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动态编译

目录