在路上

 找回密码
 立即注册
在路上 站点首页 学习 查看内容

java中HashMap的原理分析

2016-8-29 13:25| 发布者: zhangjf| 查看: 611| 评论: 0

摘要: 我们先来看这样的一道面试题: 在 HashMap 中存放的一系列键值对,其中键为某个我们自定义的类型。放入 HashMap 后,我们在外部把某一个 key 的属性进行更改,然后我们再用这个 key 从 HashMap 里取出元素,这时候 H ...

我们先来看这样的一道面试题:

在 HashMap 中存放的一系列键值对,其中键为某个我们自定义的类型。放入 HashMap 后,我们在外部把某一个 key 的属性进行更改,然后我们再用这个 key 从 HashMap 里取出元素,这时候 HashMap 会返回什么?

文中已给出示例代码与答案,但关于HashMap的原理没有做出解释。

1. 特性

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

  1. public class Person {
  2. private String name;
  3. public Person(String name) {
  4. this.name = name;
  5. }
  6. }
  7. Map<Person, String> testMap = new HashMap<>();
  8. testMap.put(new Person("hello"), "world");
  9. testMap.get(new Person("hello")); // ---> null
复制代码

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

2. 原理

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

  1. //: containers/SimpleHashMap.java
  2. // A demonstration hashed Map.
  3. import java.util.*;
  4. import net.mindview.util.*;
  5. public class SimpleHashMap<K,V> extends AbstractMap<K,V> {
  6. // Choose a prime number for the hash table size, to achieve a uniform distribution:
  7. static final int SIZE = 997;
  8. // You can't have a physical array of generics, but you can upcast to one:
  9. @SuppressWarnings("unchecked")
  10. LinkedList<MapEntry<K,V>>[] buckets =
  11. new LinkedList[SIZE];
  12. public V put(K key, V value) {
  13. V oldValue = null;
  14. int index = Math.abs(key.hashCode()) % SIZE;
  15. if(buckets[index] == null)
  16. buckets[index] = new LinkedList<MapEntry<K,V>>();
  17. LinkedList<MapEntry<K,V>> bucket = buckets[index];
  18. MapEntry<K,V> pair = new MapEntry<K,V>(key, value);
  19. boolean found = false;
  20. ListIterator<MapEntry<K,V>> it = bucket.listIterator();
  21. while(it.hasNext()) {
  22. MapEntry<K,V> iPair = it.next();
  23. if(iPair.getKey().equals(key)) {
  24. oldValue = iPair.getValue();
  25. it.set(pair); // Replace old with new
  26. found = true;
  27. break;
  28. }
  29. }
  30. if(!found)
  31. buckets[index].add(pair);
  32. return oldValue;
  33. }
  34. public V get(Object key) {
  35. int index = Math.abs(key.hashCode()) % SIZE;
  36. if(buckets[index] == null) return null;
  37. for(MapEntry<K,V> iPair : buckets[index])
  38. if(iPair.getKey().equals(key))
  39. return iPair.getValue();
  40. return null;
  41. }
  42. public Set<Map.Entry<K,V>> entrySet() {
  43. Set<Map.Entry<K,V>> set= new HashSet<Map.Entry<K,V>>();
  44. for(LinkedList<MapEntry<K,V>> bucket : buckets) {
  45. if(bucket == null) continue;
  46. for(MapEntry<K,V> mpair : bucket)
  47. set.add(mpair);
  48. }
  49. return set;
  50. }
  51. public static void main(String[] args) {
  52. SimpleHashMap<String,String> m =
  53. new SimpleHashMap<String,String>();
  54. m.putAll(Countries.capitals(25));
  55. System.out.println(m);
  56. System.out.println(m.get("ERITREA"));
  57. System.out.println(m.entrySet());
  58. }
  59. }
复制代码

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

JDK的HashMap的实现原理与之相类似,其采用链地址的hash表table存储Map.Entry:

  1. /**
  2. * The table, resized as necessary. Length MUST Always be a power of two.
  3. */
  4. transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
  5. static class Entry<K,V> implements Map.Entry<K,V> {
  6. final K key;
  7. V value;
  8. Entry<K,V> next;
  9. int hash;
  10. }
复制代码

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

  1. public V get(Object key) {
  2. if (key == null)
  3. return getForNullKey();
  4. Entry<K,V> entry = getEntry(key);
  5. return null == entry ? null : entry.getValue();
  6. }
  7. final Entry<K,V> getEntry(Object key) {
  8. if (size == 0) {
  9. return null;
  10. }
  11. int hash = (key == null) ? 0 : hash(key);
  12. for (Entry<K,V> e = table[indexFor(hash, table.length)];
  13. e != null;
  14. e = e.next) {
  15. Object k;
  16. if (e.hash == hash &&
  17. ((k = e.key) == key || (key != null && key.equals(k))))
  18. return e;
  19. }
  20. return null;
  21. }
复制代码

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

最新评论

小黑屋|在路上 ( 蜀ICP备15035742号-1 

;

GMT+8, 2025-7-7 18:22

Copyright 2015-2025 djqfx

返回顶部