热门课程

免费试听

上课方式

开班时间

当前位置: 首页 -   文章 -   新闻动态 -   正文

HashMap源码解析

知了堂姐
2024-07-09 11:12:24
0

一、HashMap介绍

HashMap位于JDK自带jar包rt.jar的java.util目录下。 HashMap是一个散列表,存储的内容是键值对映射。

HashMap继承于AbstractMap,实现了Map、Cloneable、Serializable接口 HashMap是线程不安全的,其中key、value都可以为null,且是无序的。

二、HashMap的数据结构

HashMap的底层主要是基于数组和链表来实现的,它之所以有相当快的查询速度主要是因为它是通过计算散列码来决定存储的位置。HashMap中主要是通过key的hashCode来计算hash值的,只要hashCode相同,计算出来的hash值就一样。如果存储的对象对多了,就有可能不同的对象所算出来的hash值是相同的,这就出现了所谓的hash冲突。学过数据结构的同学都知道,解决hash冲突的方法有很多,HashMap底层是通过链表来解决hash冲突的。

图中,紫色部分即代表哈希表,也称为哈希数组,数组的每个元素都是一个单链表的头节点,链表是用来解决冲突的,如果不同的key映射到了数组的同一位置处,就将其放入单链表中。

我们看看HashMap中Entry类的代码:

HashMap其实就是一个Entry数组,Entry对象中包含了键和值,其中next也是一个Entry对象,它就是用来处理hash冲突的,形成一个链表。

三、HashMap源码分析

1、关键属性

先看看HashMap类中的一些关键属性:

transient Entry[] table;//存储元素的实体数组

transient int size;//存放元素的个数

int threshold; //临界值 当实际大小超过临界值时,会进行扩容threshold = 加载因子*容量

final float loadFactor; //加载因子

transient int modCount;//被修改的次数

其中loadFactor加载因子是表示Hsah表中元素的填满的程度.

如:加载因子越大,填满的元素越多,好处是,空间利用率高了,但:冲突的机会加大了.链表长度会越来越长,查找效率降低。

反之,加载因子越小,填满的元素越少,好处是:冲突的机会减小了,但:空间浪费多了.表中的数据将过于稀疏(很多空间还没用,就开始扩容了)

冲突的机会越大,则查找的成本越高.

因此,必须在 "冲突的机会"与"空间利用率"之间寻找一种平衡与折衷. 这种平衡与折衷本质上是数据结构中有名的"时-空"矛盾的平衡与折衷.

如果机器内存足够,并且想要提高查询速度的话可以将加载因子设置小一点;相反如果机器内存紧张,并且对查询速度没有什么要求的话可以将加载因子设置大一点。不过一般我们都不用去设置它,让它取默认值0.75就好了。

2、构造方法

下面看看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);
     // Find a power of 2 >= initialCapacity
     int capacity = 1;  //初始容量
     while (capacity < initialCapacity)  //确保容量为2的n次幂,使capacity为大于initialCapacity的最小的2的n次幂
       capacity <<= 1; 
     this.loadFactor = loadFactor;
     threshold = (int)(capacity \* loadFactor);
     table = new Entry[capacity];
    init();
  }
   public HashMap(int initialCapacity) {
     this(initialCapacity, DEFAULT_LOAD_FACTOR);
  } 
   public HashMap() {
     this.loadFactor = DEFAULT_LOAD_FACTOR;
     threshold = (int)(DEFAULT_INITIAL_CAPACITY \* DEFAULT_LOAD_FACTOR);
     table = new Entry[DEFAULT_INITIAL_CAPACITY];
    init();
   }

我们可以看到在构造HashMap的时候如果我们指定了加载因子和初始容量的话就调用第一个构造方法,否则的话就是用默认的。默认初始容量为16,默认加载因子为0.75。我们可以看到上面代码中13-15行,这段代码的作用是确保容量为2的n次幂,使capacity为大于initialCapacity的最小的2的n次幂,至于为什么要把容量设置为2的n次幂,我们等下再看。

重点分析下HashMap中用得最多的两个方法put和get

3、存储数据

下面看看HashMap存储数据的过程是怎样的,首先看看HashMap的put方法:

上面程序中用到了一个重要的内部接口:Map.Entry,每个 Map.Entry 其实就是一个 key-value 对。从上面程序中可以看出:当系统决定存储 HashMap 中的 key-value 对时,完全没有考虑 Entry 中的 value,仅仅只是根据 key 来计算并决定每个 Entry 的存储位置。这也说明了前面的结论:我们完全可以把 Map 集合中的 value 当成 key 的附属,当系统决定了 key 的存储位置之后,value 随之保存在那里即可。

我们慢慢地来分析这个函数,第2和3行的作用就是处理key值为null的情况,我们看看putForNullKey(value)方法:

注意:如果key为null的话,hash值为0,对象存储在数组中索引为0的位置。即table[0]

我们再回去看看put方法中第4行,它是通过key的hashCode值计算hash码,下面是计算hash码的函数:

//计算hash值的方法 通过键的hashCode来计算
   static int hash(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);
   }

得到hash码之后就会通过hash码去计算出应该存储在数组中的索引。

踩坑记录

坑:将字符作为key,出现次数作为value存储在了hashmap中,这样当需要找那个只需要出现一次的字符时,只需要找到第一次出现次数为1的字符即可。我使用了HashMap的keySet的迭代器,进行遍历,在输入googl之前结果都是正确的,但是当输入了e之后返回了e。代码如下:

使用本地IDE debug后发现在插入e之后,e的地址在l之前,想了下,hashmap在存入数据时是基于hash算法寻址的,因此并不能保证按顺序存入数据,因此使用hashmap的迭代器并不能找到第一个次数为1字符,只能找到内部数组第一次出现次数为1的字符。更改思路:增加一个数组按顺序存放字符,随后遍历数组,将数组的值作为key获取value(次数),代码如下:

大家都在看

Java面试八股文指的是什么?Java开发岗位必...

2024-07-09 浏览次数:0

体验官招募|体验软件园高薪程序员人生,还有100...

2024-07-09 浏览次数:0

医药集团业务中台价值

2024-07-09 浏览次数:0

上万条转行贴刷爆小红书豆瓣,软件测试、程序开发竟...

2024-07-09 浏览次数:0

你正躺着吗?月薪多少,可以躺平? 其实,我也想躺...

2024-07-09 浏览次数:0

请问网络安全培训出来好找工作吗?

2024-07-09 浏览次数:0
最新资讯
HashMap源码解析 HashMap位于JDK自带jar包rt.jar的java.util目录下。 HashMap是一个散...
HashMap介绍(附面试题) JDK1.8-HashMap数据结构