一、需求

  最近开发中,总是需要使用HashMap,而为了更好的开发以及理解HashMap;因此特定重新去看HashMap的源码并写下学习笔记,以便以后查阅。

二、HashMap的学习理解  

  1、我们首先需要知道HashMap为什么会存在?

    HashMap是从Java1.2引进的基于哈希表的Map接口的一个实现,以key-value的形式存在,从而可以通过key快速存取value值。

    解释下哈希表(HashTable)——在说HahMap之前先说说Java中的数据结构,数组与链表的区别。

    数组:数组的存储区间是连续的,占用内存比较大,从而导致空间复杂度比较大,时间复杂度比较小;特点是:寻址容易,但插入删除困难。

    链表:链表的存储区间不连续,占的内存相对较小,空间复杂度也比较小,时间复杂度比较大;特点是:寻址困难,插入删除容易。

    而哈希表(HashTable)是数组与链表二者特点的综合,既满足了数据的查找方便,同时不占用太多的内容空间,使用也十分方便。

    HashMap其实也是一个线性的数组实现的,所以可以理解为其存储数据的容器就是一个线性数组。

  2、HashMap的定义

    HashMap实现了Map接口,继承AbstractMap。其中Map接口定义了键映射到值的规则,而AbstractMap类提供 Map 接口的具体实现,以最大限度地减少实现此接口所需的工作。   

    public class HashMap<K,V>

          extends AbstractMap<K,V>

          implements Map<K,V>, Cloneable, Serializable

基于哈希表的 Map 接口的实现。此实现提供所有可选的映射操作,并允许使用 null 值和 null 键。(除了非同步和允许使用 null 之外,HashMap 类与 Hashtable 大致相同。)此类不保证映射的顺序,特别是它不保证该顺序恒久不变。

  3、HashMap的构造方法摘要

    HashMap的四个构造函数:

      HashMap():构造一个具有默认初始容量(16)和加载因子(0.75)的空HashMap。

     HashMap(int initialCapacity) :构造一个带指定初始容量和默认加载因子(0.75)的空HashMap。

     HashMap(int initialCapacity, float loadFactor):构造一个带指定初始容量和加载因子的空HashMap。

     HashMap(Map<? extends K,? extends V> m) :构造一个映射关系与指定Map相同的新HashMap。

  HashMap 的实例有两个参数影响其性能:初始容量加载因子容量 是哈希表中桶的数量,初始容量只是哈希表在创建时的容量。加载因子 是哈希表在其容量自动增加之前可以达到多满的一种尺度。当哈希表中的条目数超出了加载因子与当前容量的乘积时,则要对该哈希表进行 rehash 操作(即重建内部数据结构),从而哈希表将具有大约两倍的桶数。

  通常,默认加载因子 (0.75) 在时间和空间成本上寻求一种折衷。加载因子过高虽然减少了空间开销,但同时也增加了查询成本(在大多数 HashMap 类的操作中,包括 getput 操作,都反映了这一点)。在设置初始容量时应该考虑到映射中所需的条目数及其加载因子,以便最大限度地减少 rehash 操作次数。如果初始容量大于最大条目数除以加载因子,则不会发生 rehash 操作。因此一般情况下无需修改。

  如果很多映射关系要存储在 HashMap 实例中,则相对于按需执行自动的 rehash 操作以增大表的容量来说,使用足够大的初始容量创建它将使得映射关系能更有效地存储。

HashMap构造方法的源码如下图:

    

  从源码中可以看出,每次新建一个HashMap时,都会初始化一个table数组。table数组的元素为Entry节点。

   4、HashMap的数据结构

    

  从上图看出,HashMap的底层实现还是数组,只是数组的每一项都是一条链。其中参数initialCapacity就代表了该数组的长度。

HashMap为什么能随机存取?这里用了一个小算法:

 // 存储时:
int hash = key.hashCode(); // 每个key的hash是一个固定的int值
int index = hash % Entry[].length;
Entry[index] = value; // 取值时:
int hash = key.hashCode();
int index = hash % Entry[].length;
return Entry[index];

  5、HashMap存取实现put(key,values)

  

  通过源码我们可以清晰看到HashMap保存数据的过程为:首先判断key是否为null,若为null,则直接调用putForNullKey方法。若不为空则先计算key的hash值,然后根据hash值搜索在table数组中的索引位置,如果table数组在该位置处有元素,则通过比较是否存在相同的key,若存在则覆盖原来key的value(这样就保证了HashMap中没有两个相同的key),否则将该元素保存在链头(最先保存的元素放在链尾)。若table在该处没有元素,则直接保存。

  首先,我们先看看当key为null时的putForNullKey方法源码:

  null key总是存放在Entry[]数组的第一个元素。

  

  其次,我们再看到HashMap的核心之一:hash方法,该方法为一个纯粹的数学计算,就是计算h的hash值。

  

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

  再来看看HashMap的核心之二:indexFor方法

HashMap存取时,都需要计算当前key应该对应Entry[]数组哪个元素,即计算数组下标;算法如下:

  

  HashMap的底层数组长度总是2的n次方。当length为2的n次方时,h&(length - 1)就相当于对length取模,这意味着数组下标相同,并不表示hashCode相同。而且速度比直接取模快得多。

  最后,再看看addEntry方法,addEntry(hash, key, value, i);

  

这个方法中有两点需要注意:

  •       链的产生。系统总是将新的Entry对象添加到bucketIndex处。如果bucketIndex处已经有了对象,那么新添加的Entry对象将指向原有的Entry对象,形成一条Entry链,但是若bucketIndex处没有Entry对象,也就是e==null,那么新添加的Entry对象指向null,也就不会产生Entry链了。
  •       扩容问题。

随着HashMap中元素的数量越来越多,发生碰撞的概率就越来越大,所产生的链表长度就会越来越长,这样势必会影响HashMap的速度,为了保证HashMap的效率,系统必须要在某个临界点进行扩容处理。该临界点在当HashMap中元素的数量等于table数组长度*加载因子。但是扩容是一个非常耗时的过程,因为它需要重新计算这些数据在新table数组中的位置并进行复制处理。所以如果我们已经预知HashMap中元素的个数,那么预设元素的个数能够有效的提高HashMap的性能

  6、HashMap存取实现get(key,values)

  

  HashMap在存储过程中并没有将key,value分开来存储,而是作为一个Entry对象。在存储的过程中,系统根据key的hashcode来决定Entry在table数组中的存储位置,在取的过程中同样根据key的hashcode取出相对应的Entry对象。

  7、再散列rehash过程

  当哈希表的容量超过默认容量时,则会调整table的大小。当容量已经达到最大可能值时,那么该方法就将容量调整到Integer.MAX_VALUE返回,这时,需要创建一张新表,将原表的映射到新表中。

  

  

PS:如果觉得文章有地方写得不对,请指正我们共同学习;如果你觉得文章对你有所帮助,别忘了推荐或者分享!

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

本文在撰写中参考过以下两位博主的文章:

http://blog.csdn.net/vking_wang/article/details/14166593

http://www.cnblogs.com/chenssy/p/3521565.html

HashMap源码剖析及实现原理分析(学习笔记)的更多相关文章

  1. 老李推荐:第6章8节《MonkeyRunner源码剖析》Monkey原理分析-事件源-事件源概览-小结

    老李推荐:第6章8节<MonkeyRunner源码剖析>Monkey原理分析-事件源-事件源概览-小结   本章我们重点围绕处理网络过来的命令的MonkeySourceNetwork这个事 ...

  2. 老李推荐:第6章7节《MonkeyRunner源码剖析》Monkey原理分析-事件源-事件源概览-注入按键事件实例

    老李推荐:第6章7节<MonkeyRunner源码剖析>Monkey原理分析-事件源-事件源概览-注入按键事件实例   poptest是国内唯一一家培养测试开发工程师的培训机构,以学员能胜 ...

  3. 老李推荐:第6章6节《MonkeyRunner源码剖析》Monkey原理分析-事件源-事件源概览-命令队列

    老李推荐:第6章6节<MonkeyRunner源码剖析>Monkey原理分析-事件源-事件源概览-命令队列   事件源在获得字串命令并把它翻译成对应的MonkeyEvent事件后,会把这些 ...

  4. 老李推荐:第6章4节《MonkeyRunner源码剖析》Monkey原理分析-事件源-事件源概览-翻译命令字串

    老李推荐:第6章4节<MonkeyRunner源码剖析>Monkey原理分析-事件源-事件源概览-翻译命令字串   poptest是国内唯一一家培养测试开发工程师的培训机构,以学员能胜任自 ...

  5. 老李推荐:第6章5节《MonkeyRunner源码剖析》Monkey原理分析-事件源-事件源概览-事件

    老李推荐:第6章5节<MonkeyRunner源码剖析>Monkey原理分析-事件源-事件源概览-事件   从网络过来的命令字串需要解析翻译出来,有些命令会在翻译好后直接执行然后返回,但有 ...

  6. 老李推荐:第6章3节《MonkeyRunner源码剖析》Monkey原理分析-事件源-事件源概览-命令翻译类

    老李推荐:第6章3节<MonkeyRunner源码剖析>Monkey原理分析-事件源-事件源概览-命令翻译类   每个来自网络的字串命令都需要进行解析执行,只是有些是在解析的过程中直接执行 ...

  7. 老李推荐:第6章2节《MonkeyRunner源码剖析》Monkey原理分析-事件源-事件源概览-获取命令字串

    老李推荐:第6章2节<MonkeyRunner源码剖析>Monkey原理分析-事件源-事件源概览-获取命令字串   从上一节的描述可以知道,MonkeyRunner发送给Monkey的命令 ...

  8. 老李推荐:第5章7节《MonkeyRunner源码剖析》Monkey原理分析-启动运行: 循环获取并执行事件 - runMonkeyCycles

    老李推荐:第5章7节<MonkeyRunner源码剖析>Monkey原理分析-启动运行: 循环获取并执行事件 - runMonkeyCycles   poptest是国内唯一一家培养测试开 ...

  9. 老李推荐:第5章6节《MonkeyRunner源码剖析》Monkey原理分析-启动运行: 初始化事件源

    老李推荐:第5章6节<MonkeyRunner源码剖析>Monkey原理分析-启动运行: 初始化事件源   poptest是国内唯一一家培养测试开发工程师的培训机构,以学员能胜任自动化测试 ...

随机推荐

  1. Java设计模式(12)——结构型模式之门面模式(Facade)

    一.概述 概念 简要示意图(没有一个统一的UML图) 角色 门面角色:门面模式核心,它被客户端调用,并且熟悉子系统   子系统角色:子系统,子系统并不知道门面的存在,门面对它来说只不过是另外一个客户端 ...

  2. 20145202马超《网络对抗》Exp8 Web基础

    1.本实践的具体要求有: (1).Web前端HTML(1分) 能正常安装.启停Apache.理解HTML,理解表单,理解GET与POST方法,编写一个含有表单的HTML. (2).Web前端javas ...

  3. mfc通过MapWinGIS控件读取shp文件(通过#import实现)

    在MFC工程中想使用MapWinGIS组件,有多种方法可以实现, 第一种方法,#Import来实现 1.首先注册MapWinGIS ActiveX组件, 2.新建一个单文档工程:MapGis,为控件添 ...

  4. Redis系列七 主从复制(Master/Slave)

    主从复制(Master/Slave) 1.是什么 也就是我们所说的主从复制,主机数据更新后根据配置和策略,自动同步到备机的master/slaver机制,Master以写为主,Slave以读为主. 2 ...

  5. Fat Jar - Myeclipse插件安装使用方法- 完美解决

    Eclipse可以安装一个叫Fat Jar的插件,用这个插件打包非常方便,Fat Jar的功能非常强大. 工具/原料 Eclipse Kepler Fat Jar 方法/步骤 1 Fat Jar功能非 ...

  6. docker in docker

    docker run --rm可以从一个镜像启动容器,并在容器执行完成后自动删除,这在计算任务中非常有用. 例如,我们通过以下步骤完成计算任务容器的启动: 1 将输入数据通过卷挂载方式连接到计算任务容 ...

  7. java中的比较:instanceof、equals(hashcode)、==

    import javassist.expr.Instanceof; class Person{ String s; Person(String s){ this.s=s; } } class Man ...

  8. Android intel X86 图像渲染

    最近几天有个项目需要在intel 芯片的系统上集成我们的视频通话软件.之前只是在ARM平台上使用,对于intel 没测试过,直接运行apk后,本端渲染的图像出错,渲染出的图像很像I420被作为RGB5 ...

  9. 【system.date】使用说明

    对象:system.date 说明:提供一系列针对日期类型的操作 目录: 方法 返回 说明  system.date.isDate( date_string )  [True | False]  判断 ...

  10. leetcode-颜色分类

     颜色分类     给定一个包含红色.白色和蓝色,一共 n 个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色.白色.蓝色顺序排列. 此题中,我们使用整数 0. 1 和 2 分别表示 ...