JDK1.8之前

HashMap

在jdk1.8之前HashMap底层采用的数组+链表;

HashMap是基于哈希表的Map接口的非同步实现。此实现提供所有可选的映射操作,并允许使用null值和null键。HashMap储存的是键值对,HashMap很快。此类不保证映射的顺序,特别是它不保证该顺序恒久不变。

HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体。

数组:存储区间连续,占用内存严重,寻址容易,插入删除困难;
链表:存储区间离散,占用内存比较宽松,寻址困难,插入删除容易;
Hashmap综合应用了这两种数据结构,实现了寻址容易,插入删除也容易。
hashMap的结构示意图如下:

hashcode方法的底层算法 java hashmap底层原理_HashMap底层数据结构

  • HashMap是基于hashing的原理,我们使用put(key, value)存储对象到HashMap中,使用get(key)从HashMap中获取对象。
  • 当我们给put(key, value)方法传递键和值时,它先调用key.hashCode()方法,返回的hashCode值,用于找到bucket位置,来储存Entry对象。
  • Map提供了一些常用方法,如keySet()、entrySet()等方法。
    keySet()方法返回值是Map中key值的集合;entrySet()的返回值也是返回一个Set集合,此集合的类型为Map.Entry。
  •  “如果两个key的hashcode相同,你如何获取值对象?”答案:当我们调用get(key)方法,HashMap会使用key的hashcode值,找到bucket位置,然后获取值对象。
  • “如果有两个值对象,储存在同一个bucket ?”答案:将会遍历链表直到找到值对象。
  • “这时会问因为你并没有值对象去比较,你是如何确定确定找到值对象的?”答案:找到bucket位置之后,会调用keys.equals()方法,去找到链表中正确的节点,最终找到要找的值对象。

概要:

  • HashMap基于hashing原理,我们通过put(key,value)和get(key)方法储存和获取对象。
  • 当储存对象时,我们将键值对传递给put(key,value)方法时,它调用键对象key的hashCode()方法来计算hashcode,然后找到bucket位置,来储存值对象value。
  • 当获取对象时,通过key的equals()方法找到正确的键值对key-value,然后返回值对象value。
  • HashMap使用链表来解决碰撞问题,当发生碰撞了,对象将会储存在链表的下一个节点中。
  • HashMap在每个链表节点中,储存 键值对key-value 对象。
  • 当两个不同的键对象key的hashcode相同时,会发生什么?它们会储存在同一个bucket位置的链表中,并通过键对象key的equals()方法用来找到键值对key-value。

JDK1.8——》

HashMap底层数据结构

在Jdk1.8中HashMap的实现方式做了一些改变,但是基本思想还是没有变得,只是在一些地方做了优化,下面来看一下这些改变的地方,数据结构的存储由数组+链表的方式,变化为数组+链表+红黑树的存储方式,在性能上进一步得到提升。

数据存储方式

hashcode方法的底层算法 java hashmap底层原理_HashMap底层数据结构_02

总结

  • HashMap采用hash算法来决定Map中key的存储,并通过hash算法来增加集合的大小。
  • hash表里可以存储元素的位置称为桶(bucket),如果通过key计算hash值发生冲突时,那么将采用链表的形式,来存储元素。
  • HashMap的扩容操作是一项很耗时的任务,所以如果能估算Map的容量,最好给它一个默认初始值,避免进行多次扩容。
  • HashMap的线程是不安全的,多线程环境中推荐是ConcurrentHashMap。

1、实现原理

  • HashMap是基于hashing的原理,我们使用put(key, value)存储对象到HashMap中,使用get(key)从HashMap中获取对象。
  • 当我们给put(key, value)方法传递键和值时,它先调用key.hashCode()方法,返回的hashCode值,用于找到bucket位置,来储存Entry对象。
  • Map提供了一些常用方法,如keySet()、entrySet()等方法。
    keySet()方法返回值是Map中key值的集合;entrySet()的返回值也是返回一个Set集合,此集合的类型为Map.Entry。
  •  “如果两个key的hashcode相同,你如何获取值对象?”答案:当我们调用get(key)方法,HashMap会使用key的hashcode值,找到bucket位置,然后获取值对象。
  • “如果有两个值对象,储存在同一个bucket ?”答案:将会遍历链表直到找到值对象。
  • “这时会问因为你并没有值对象去比较,你是如何确定确定找到值对象的?”答案:找到bucket位置之后,会调用keys.equals()方法,去找到链表中正确的节点,最终找到要找的值对象。

完美的回答:

  • HashMap基于hashing原理,我们通过put(key,value)和get(key)方法储存和获取对象。
  • 当储存对象时,我们将键值对传递给put(key,value)方法时,它调用键对象key的hashCode()方法来计算hashcode,然后找到bucket位置,来储存值对象value。
  • 当获取对象时,通过key的equals()方法找到正确的键值对key-value,然后返回值对象value。
  • HashMap使用链表来解决碰撞问题,当发生碰撞了,对象将会储存在链表的下一个节点中。
  • HashMap在每个链表节点中,储存 键值对key-value 对象。
  • 当两个不同的键对象key的hashcode相同时,会发生什么?它们会储存在同一个bucket位置的链表中,并通过键对象key的equals()方法用来找到键值对key-value。

因为HashMap的好处非常多,我曾经在我的应用中使用HashMap作为缓存。因为金融领域非常多的运用Java,也出于性能的考虑,我们会经常用到HashMap和ConcurrentHashMap。

2、底层的数据结构

HashMap的底层主要是基于数组和链表来实现的,它之所以有相当快的查询速度主要是因为它是通过计算散列码来决定存储的位置。

  • HashMap中主要是通过key的hashCode来计算hash值的,只要hashCode相同,计算出来的hash值就一样。
  • 如果存储的对象对多了,就有可能不同的对象所算出来的hash值是相同的,这就出现了所谓的hash冲突。
  • 学过数据结构的同学都知道,解决hash冲突的方法有很多,HashMap底层是通过链表来解决hash冲突的。

补充知识:

  • HashMap是基于哈希表的 Map 接口的实现。
  • 此实现提供所有可选的映射操作,并允许使用 null 值和 null 键。(除了非同步和允许使用 null 之外,HashMap 类与 Hashtable 大致相同。)
  • 此类不保证映射的顺序,特别是它不保证该顺序恒久不变。
  • 值得注意的是HashMap不是线程安全的,如果想要线程安全的HashMap,可以通过Collections类的静态方法synchronizedMap获得线程安全的HashMap。
Map map = Collections.synchronizedMap(new HashMap());
  • HashMap结合了ArrayList与LinkedList两个实现的优点,虽然HashMap并不会向List的两种实现那样,在某项操作上性能较高,但是在基本操作(get 和 put)上具有稳定的性能。