千家信息网

HashMap底层原理分析

发表于:2025-01-20 作者:千家信息网编辑
千家信息网最后更新 2025年01月20日,小编给大家分享一下HashMap底层原理分析,希望大家阅读完这篇文章之后都有所收获,下面让我们一起去探讨吧!众所周知,HashMap是一个用于存储Key-Value键值对的集合,每一个键值对也叫做En
千家信息网最后更新 2025年01月20日HashMap底层原理分析

小编给大家分享一下HashMap底层原理分析,希望大家阅读完这篇文章之后都有所收获,下面让我们一起去探讨吧!

众所周知,HashMap是一个用于存储Key-Value键值对的集合,每一个键值对也叫做Entry。这些个键值对(Entry)分散存储在一个数组当中,这个数组就是HashMap的主干

1. 特性

我们可以用任何类作为HashMap的key,但是对于这些类应该有什么限制条件呢?且看下面的代码:

public class Person {   private String name;   public Person(String name) {       this.name = name;   }}Map testMap = new HashMap();testMap.put(new Person("hello"), "world");testMap.get(new Person("hello")); // ---> null

本是想取出具有相等字段值Person类的value,结果却是null。对HashMap稍有了解的人看出来——Person类并没有override hashcode方法,导致其继承的是Object的hashcode(返回是其内存地址),两次new出来的Person对象并不equals——这也是为什么在工程项目中常用不变类(如String、Integer等)做为HashMap的key的原因。那么,HashMap是如何利用hashcode给key做索引的呢?

2. 原理

首先,我们来看《Thinking in Java》中一个简单HashMap的实现方案:

//: containers/SimpleHashMap.java// A demonstration hashed Map.import java.util.*;import net.mindview.util.*;public class SimpleHashMap extends AbstractMap { // Choose a prime number for the hash table size, to achieve a uniform distribution: static final int SIZE = 997; // You can't have a physical array of generics, but you can upcast to one: @SuppressWarnings("unchecked") LinkedList>[] buckets =   new LinkedList[SIZE]; public V put(K key, V value) {   V oldValue = null;   int index = Math.abs(key.hashCode()) % SIZE;   if(buckets[index] == null)     buckets[index] = new LinkedList>();   LinkedList> bucket = buckets[index];   MapEntry pair = new MapEntry(key, value);   boolean found = false;   ListIterator> it = bucket.listIterator();   while(it.hasNext()) {     MapEntry iPair = it.next();     if(iPair.getKey().equals(key)) {       oldValue = iPair.getValue();       it.set(pair); // Replace old with new       found = true;       break;     }   }   if(!found)     buckets[index].add(pair);   return oldValue; } public V get(Object key) {   int index = Math.abs(key.hashCode()) % SIZE;   if(buckets[index] == null) return null;   for(MapEntry iPair : buckets[index])     if(iPair.getKey().equals(key))       return iPair.getValue();   return null; } public Set> entrySet() {   Set> set= new HashSet>();   for(LinkedList> bucket : buckets) {     if(bucket == null) continue;     for(MapEntry mpair : bucket)       set.add(mpair);   }   return set; } public static void main(String[] args) {   SimpleHashMap m =     new SimpleHashMap();   m.putAll(Countries.capitals(25));   System.out.println(m);   System.out.println(m.get("ERITREA"));   System.out.println(m.entrySet()); }}

SimpleHashMap构造一个hash表来存储key,hash函数是取模运算Math.abs(key.hashCode()) % SIZE,采用链表法解决hash冲突;buckets的每一个槽位对应存放具有相同(hash后)index值的Map.Entry,如下图所示: JDK的HashMap的实现原理与之相类似,其采用链地址的hash表table存储Map.Entry:

/*** The table, resized as necessary. Length MUST Always be a power of two.*/transient Entry[] table = (Entry[]) EMPTY_TABLE;static class Entry implements Map.Entry {   final K key;   V value;   Entry next;   int hash;   …}

Map.Entry的index是对key的hashcode进行hash后所得。当要get key对应的value时,则对key计算其index,然后在table中取出Map.Entry即可得到,具体参看代码:

public V get(Object key) {   if (key == null)       return getForNullKey();   Entry entry = getEntry(key);   return null == entry ? null : entry.getValue();}final Entry getEntry(Object key) {   if (size == 0) {       return null;   }   int hash = (key == null) ? 0 : hash(key);   for (Entry e = table[indexFor(hash, table.length)];        e != null;        e = e.next) {       Object k;       if (e.hash == hash &&           ((k = e.key) == key || (key != null && key.equals(k))))           return e;   }   return null;}

可见,hashcode直接影响HashMap的hash函数的效率——好的hashcode会极大减少hash冲突,提高查询性能。同时,这也解释开篇提出的两个问题:如果自定义的类做HashMap的key,则hashcode的计算应涵盖构造函数的所有字段,否则有可能得到null。

看完了这篇文章,相信你对"HashMap底层原理分析"有了一定的了解,如果想了解更多相关知识,欢迎关注行业资讯频道,感谢各位的阅读!

0