前言
HashMap是Java中常用的集合,而且HashMap的一些思想,对于我们平时解决业务上的一些问题,在思路上有帮助,基于此,本篇博客将分析HashMap底层设计思想,并手写一个迷你版的HashMap!
对HashMap的思考
HashMap底层数据结构
第一,如图所示,HashMap有3个要素:hash函数+数组+单链表
第二,对于hash函数而言,需要考虑些什么?
要快,对于给定的Key,要能够快速计算出在数组中的index。那么什么运算够快呢?显然是位运算!
要均匀分布,要较少碰撞。说白了,我们希望通过hash函数,让数据均匀分布在数组中,不希望大量数据发生碰撞,导致链表过长。那么怎么办到呢?也是利用位运算,通过对数据的二进制的位进行移动,让hash函数得到的数据散列开来,从而减低了碰撞的概率。
如果发生了碰撞怎么办?上面的图其实已经说明了JDK的HashMap是如何处理hash冲突的,就是通过单链表解决的。那么除了这个方法,还有其他思路么?比如说,如果发生冲突,那么记下这个冲突的位置为index,然后在加上固定步长,即index+step,找到这个位置,看一下是否仍然冲突,如果继续冲突,那么按照这个思路,继续加上固定步长。其实这就是所谓的线性探测来解决Hash冲突的方法!
通过写一个迷你版的HashMap来深刻理解
定义接口
接口
定义一个接口,对外暴露快速存取的方法。
注意MyMap接口内部定义了一个内部接口Entry。
接口实现
MyHashMap定义
HashMap的要素之一,就是数组,自然在这里,我们要定义数组,数组的初始化大小,还要考虑扩容的阀值。
看MyHashMap的构造
构造方法
构造方法有什么好说的呢?
仔细观察下,你会发现,其实这里使用到了“门面模式”。这里的2个构造方法其实指向的是同一个,但是对外却暴露了2个“门面”!
Entry
Entry
HashMap的要素之一,单链表的体现就在这里!
看put如何实现
put
第一,要考虑是否扩容?
HashMap中的Entry的数量(数组以及单链表中的所有Entry)是否达到阀值?
第二,如果扩容,意味着新生成一个Entry[],不仅如此还得重新散列。
第三,要根据Key计算出在Entry[]中的位置,定位后,如果Entry[]中的元素为null,那么可以放入其中,如果不为空,那么得遍历单链表,要么更新value,要么形成一个新的Entry“挤压”单链表!
hash函数
MyHashMap提供的hash函数
JDK的HashMap提供的hash函数
我这里参考了JDK的HashMap的hash函数的实现,这里也再次说明了:要想散列均匀,就得进行二进制的位运算!
resize和rehash
resize/rehash
这里可以看出,对于HashMap而言,如果频繁进行resize/rehash操作,是会影响性能的。
resize/rehash的过程,就是数组变大,原来数组中的entry元素一个个的put到新数组的过程,需要注意的是一些状态变量的改变。
get实现
get
get很简单,只需要注意在遍历单链表的过程中使用== or equals来判断下即可。
Test测试
利用MyHashMap进行存取
运行结果
result
OK,一个迷你版的HashMap就写好了,你学到了么?
注:关注作者微信公众号,了解更多分布式架构、微服务、netty、MySQL、spring、JVM、算法、性能优化、等知识点。
package com.suning.map; import java.util.ArrayList; import java.util.List; /** * Created by 17030057 on 2018/8/16. */ public class MyHashMap<K,V> implements MyMap<K,V> { private static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; //数组默认长度 private static final int MAXIMUM_CAPACITY = 1 << 30;//数组最大长度 private static final float DEFAULT_LOAD_FACTOR = 0.75f; //数组扩容阀值默认比例 private Entry<K,V>[] table = null; //数组 private int initialCapacity;//数组长度 private float loadFactor;//数组扩容阀值比例 private int entryUseSize; //map中entry数量 public MyHashMap(){ this(DEFAULT_INITIAL_CAPACITY,DEFAULT_LOAD_FACTOR); } public MyHashMap(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.initialCapacity = initialCapacity; this.loadFactor = loadFactor; table = new Entry[this.initialCapacity]; } //单链表(可以转换成list) class Entry<K,V> implements MyMap.Entry<K,V> { private K key; private V value; private Entry<K,V> next; public Entry() { } public Entry(K key,V value,Entry<K,V> next) { this.key = key; this.value = value; this.next = next; } @Override public K getKey() { return key; } @Override public V getValue() { return value; } } //hash函数 让key均匀分布 private int hash(K k) { int hashCode = k.hashCode(); hashCode ^= (hashCode >>> 20) ^ (hashCode >>> 12); return hashCode ^ (hashCode >>> 7) ^ (hashCode >>> 4); } //扩容 private void resize(int newCapacity) { Entry[] newTable = new Entry[newCapacity]; //修改数组大小 initialCapacity = newCapacity; entryUseSize = 0; //重新hash List<Entry<K,V>> entryList = new ArrayList<>();//得到老entry链表 for (Entry<K,V> entry : table) { if(null != entry) { do { entryList.add(entry); entry = entry.next; } while (entry != null); } } //覆盖就数组 if(newTable.length > 0) { table = newTable; } //重新组建hashMap for (Entry<K,V> entry : entryList) { put(entry.getKey(),entry.getValue()); } } @Override public V put(K k, V v) { V oldValue = null; //是否需要扩容 if(entryUseSize >= initialCapacity*loadFactor) { resize(2 * initialCapacity); } //得出hash值,计算位置 int index = hash(k) & (initialCapacity - 1); if (table[index] == null) { table[index] = new Entry<K,V>(k,v,null); ++entryUseSize; } else { Entry<K,V> entry = table[index]; Entry<K,V> e = entry; while (e == entry) { if(k == e.getKey() || k.equals(e.getKey())) { oldValue = e.value; e.value = v; return oldValue; } e = e.next; } table[index] = new Entry<K,V>(k,v,entry); ++entryUseSize; } return oldValue; } @Override public V get(K k) { int index = hash(k) & (initialCapacity - 1); if(table[index] == null) { return null; } else { Entry<K,V> entry = table[index]; do { if(k == entry.getKey() || k.equals(entry.getKey())) { return entry.value; } entry = entry.next; } while (entry != null); } return null; } public static void main(String[] args) { System.out.println(1 << 5); System.out.println(1 << 30); System.out.println(16 >>> 2); // MyHashMap<String,String> map = new MyHashMap<>(); // for (int i =0;i<1000;i++) { // map.put("key"+i , "value"+i); // } // for (int i =0;i<1000;i++) { // System.out.println(map.get("key"+i)); // } } }
相关推荐
在深入探讨HashMap的实现原理之前,我们需要了解两个关键的接口方法:`hashCode()`和`equals()`。 根据《Effective JAVA》的建议,当重写`equals()`方法时,也应重写`hashCode()`方法。这是因为在HashMap中,`...
"基于HashMap的用户标签处理兼Java中HashMap实现原理研究" 本文研究了基于HashMap的用户标签处理方法,并对Java中HashMap的实现原理进行了深入研究。HashMap是一种高效的数据结构,可以快速地存储和检索数据。本文...
hashmap实现原理.pdf
《Jdk1.8中的HashMap实现原理》 HashMap作为Java编程语言中常用的数据结构,它在Jdk1.8中的实现结合了哈希表、链表以及红黑树的特性,提供高效且灵活的键值对存储功能。本文将深入探讨HashMap的内部结构、工作原理...
总结起来,JDK1.8中的HashMap实现原理主要包括以下几个要点: 1. 链表散列结构:数组+链表,链表用于处理哈希碰撞。 2. 红黑树优化:当链表长度超过8时,转换为红黑树,减少查找、插入和删除的时间复杂度。 3. 内部...
以下是对JDK 1.8 HashMap实现原理的详细解释。 首先,HashMap是一个基于哈希表的数据结构,它实现了Map接口。它不保证映射的顺序,并且是非同步的。这意味着在多线程环境中,如果不进行同步控制,可能会出现并发...
以下是对HashMap实现原理的详细解析。 首先,HashMap内部使用了一个瞬时变量数组`table`(也称为“桶”)来存储键值对。桶是一个Entry对象数组,它的大小可以动态调整,并且长度必须是2的幂。初始时,`table`是一个...
Java8之后新增挺多新东西,接下来通过本文给大家介绍Java8 HashMap的实现原理分析,对java8 hashmap实现原理相关知识感兴趣的朋友一起学习吧
综上所述,理解HashMap的实现原理对于优化Java程序性能至关重要,尤其是在处理大量数据或并发场景时。在实际应用中,应根据具体需求选择合适的数据结构,例如,如果需要线程安全,可以选择ConcurrentHashMap;如果对...
HashMap底层原理.md
根据提供的文件信息,以下是对JDK 8.0中HashMap实现原理的详细知识点介绍: 1. HashMap概述 HashMap是Java集合框架的一部分,它实现了Map接口,用于存储键值对。其核心特点在于基于哈希表的映射机制,能够通过键...
### JDK 7.0集合系列解析之HashMap实现原理 #### 一、HashMap概述 HashMap是Java集合框架中Map接口的典型实现之一,主要用于存储键值对(Key-Value)数据。其特性如下: - 线程不安全,方法为非同步方法,因此多...
在Java编程语言中,集合框架是开发者日常工作中不可或缺的一部分,HashMap作为其中的重要成员,它的实现原理对于理解Java性能优化和数据结构有深远的意义。HashMap是一个基于哈希表的数据结构,它实现了Map接口,...
HashMap原理的深入理解 HashMap是基于哈希表的Map接口的非同步实现,提供了所有可选的映射操作,并允许使用null值和null键。HashMap储存的是键值对,HashMap很快。此类不保证映射的顺序,特别是它不保证该顺序恒久...
Java HashMap原理分析 Java HashMap是一种基于哈希表的数据结构,它的存储原理是通过将Key-Value对存储在一个数组中,每个数组元素是一个链表,链表中的每个元素是一个Entry对象,Entry对象包含了Key、Value和指向...
HashMap的底层原理主要依赖于哈希表,这是一种数据结构,它通过计算键的哈希码来实现高效的查找操作。 在HashMap中,每个元素都是一个键值对,存储在一个Entry对象中。当向HashMap添加键值对时,首先会计算键的哈希...
HashMap的实现原理基于哈希表,它的核心是通过哈希函数将键(key)映射到一个数组的特定位置,从而实现快速访问。本文将深入解析HashMap的put和get操作的内部机制。 首先,HashMap的底层数据结构是一个动态扩容的...
### HashMap的实现原理 #### 1. HashMap概述 HashMap 是 Java 集合框架中一个非常重要的类,它实现了 Map 接口,并提供了基于哈希表的存储方式。与其它 Map 实现不同的是,HashMap 允许使用 `null` 键和 `null` 值...