面试的时候经常会遇见诸如:”java中的HashMap是怎么工作的”、”HashMap的get和put内部的工作原理”这样的问题。

本文将用一个简单的例子来解释下HashMap内部的工作原理。

首先我们从一个例子开始,而不仅仅是从理论上,这样,有助于更好地理解,然后,我们来看下get和put到底是怎样工作的。

我们来看个非常简单的例子。

有一个”国家”(Country)类,我们将要用Country对象作为key,它的首都的名字(String类型)作为value。

下面的例子有助于我们理解key-value对在HashMap中是如何存储的。

1. Country.java

  1. public class Country {
  2. String name;
  3. long population;
  4.  
  5. public Country(String name, long population) {
  6. super();
  7. this.name = name;
  8. this.population = population;
  9. }
  10.  
  11. public String getName() {
  12. return name;
  13. }
  14.  
  15. public void setName(String name) {
  16. this.name = name;
  17. }
  18.  
  19. public long getPopulation() {
  20. return population;
  21. }
  22.  
  23. public void setPopulation(long population) {
  24. this.population = population;
  25. }
  26.  
  27. // If length of name in country object is even then return 31(any random
  28. // number) and if odd then return 95(any random number).
  29. // This is not a good practice to generate hashcode as below method but I am
  30. // doing so to give better and easy understanding of hashmap.
  31. @Override
  32. public int hashCode() {
  33. if (this.name.length() % 2 == 0)
  34. return 31;
  35. else
  36. return 95;
  37. }
  38.  
  39. @Override
  40. public boolean equals(Object obj) {
  41. Country other = (Country) obj;
  42. if (name.equalsIgnoreCase((other.name)))
  43. return true;
  44. return false;
  45. }
  46.  
  47. }

2. HashMapStructure.java(main class)

  1. import java.util.HashMap;
  2. import java.util.Iterator;
  3.  
  4. public class HashMapStructure {
  5. /**
  6. * @author Arpit Mandliya
  7. */
  8. public static void main(String[] args) {
  9.  
  10. Country india = new Country("India", 1000);
  11. Country japan = new Country("Japan", 10000);
  12. Country france = new Country("France", 2000);
  13. Country russia = new Country("Russia", 20000);
  14.  
  15. HashMap<Country, String> countryCapitalMap = new HashMap<Country, String>();
  16. countryCapitalMap.put(india, "Delhi");
  17. countryCapitalMap.put(japan, "Tokyo");
  18. countryCapitalMap.put(france, "Paris");
  19. countryCapitalMap.put(russia, "Moscow");
  20.  
  21. Iterator<Country> countryCapitalIter = countryCapitalMap.keySet().iterator();// put debug point at this line
  22. while (countryCapitalIter.hasNext()) {
  23. Country countryObj = countryCapitalIter.next();
  24. String capital = countryCapitalMap.get(countryObj);
  25. System.out.println(countryObj.getName() + "----" + capital);
  26. }
  27. }
  28.  
  29. }

现在,在第21行设置一个断点,在项目上右击->调试运行(debug as)->java应用(java application)。程序会停在23行,然后在countryCapitalMap上右击,选择“查看”(watch)。将会看到如下的结构:

从上图可以观察到以下几点:

1.有一个叫做table大小是16的Entry数组。

2.这个table数组存储了Entry类的对象。HashMap类有一个叫做Entry的内部类。这个Entry类包含了key-value作为实例变量。

我们来看下Entry类的结构。Entry类的结构:

  1. static class Entry implements Map.Entry
  2. {
  3. final K key;
  4. V value;
  5. Entry next;
  6. final int hash;
  7. ...//More code goes here
  8. }

每当往hashmap里面存放key-value对的时候,都会为它们实例化一个Entry对象,这个Entry对象就会存储在前面提到的Entry数组table中。

现在你一定很想知道,上面创建的Entry对象将会存放在具体哪个位置(在table中的精确位置)。

答案就是,根据key的hashcode()方法计算出来的hash值(来决定)。hash值用来计算key在Entry数组的索引。

现在,如果你看下上图中数组的索引10,它有一个叫做HashMap$Entry的Entry对象。

我们往hashmap放了4个key-value对,但是看上去好像只有2个元素!!!这是因为,如果两个元素有相同的hashcode,它们会被放在同一个索引上。问题出现了,该怎么放呢?原来它是以链表(LinkedList)的形式来存储的(逻辑上)。

上面的country对象的key-value的hash值是如何计算出来的。

Japan的Hash值是95,它的长度是奇数。

India的Hash值是95,它的长度是奇数。

Russia的Hash值是31,它的长度是偶数。

France的Hash值是31,它的长度是偶数。

下图会清晰的从概念上解释下链表。

链表:hash、key、next(hash、key、next(...)、value)、value

所以,现在假如你已经很好地了解了hashmap的结构,让我们看下put和get方法。

Put :

让我们看下put方法的实现:

  1. /**
  2. * Associates the specified value with the specified key in this map. If the
  3. * map previously contained a mapping for the key, the old value is
  4. * replaced.
  5. *
  6. * @param key
  7. * key with which the specified value is to be associated
  8. * @param value
  9. * value to be associated with the specified key
  10. * @return the previous value associated with <tt>key</tt>, or <tt>null</tt>
  11. * if there was no mapping for <tt>key</tt>. (A <tt>null</tt> return
  12. * can also indicate that the map previously associated
  13. * <tt>null</tt> with <tt>key</tt>.)
  14. */
  15. public V put(K key, V value) {
  16. if (key == null)
  17. return putForNullKey(value);
  18. int hash = hash(key.hashCode());
  19. int i = indexFor(hash, table.length);
  20. for (Entry<k, V> e = table[i]; e != null; e = e.next) {
  21. Object k;
  22. if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
  23. V oldValue = e.value;
  24. e.value = value;
  25. e.recordAccess(this);
  26. return oldValue;
  27. }
  28. }
  29.  
  30. modCount++;
  31. addEntry(hash, key, value, i);
  32. return null;
  33. }

现在我们一步一步来看下上面的代码。

1.对key做null检查。如果key是null,会被存储到table[0],因为null的hash值总是0。

2.key的hashcode()方法会被调用,然后计算hash值。hash值用来找到存储Entry对象的数组的索引。有时候hash函数可能写的很不好,所以JDK的设计者添加了另一个叫做hash()的方法,它接收刚才计算的hash值作为参数。

3.indexFor(hash,table.length)用来计算在table数组中存储Entry对象的精确的索引。

4.在我们的例子中已经看到,如果两个key有相同的hash值(也叫冲突),他们会以链表的形式来存储。所以,这里我们就迭代链表。

  • 如果在刚才计算出来的索引位置没有元素,直接把Entry对象放在那个索引上。
  • 如果索引上有元素,然后会进行迭代,一直到Entry->next是null。当前的Entry对象变成链表的下一个节点。
  • 如果我们再次放入同样的key会怎样呢?逻辑上,它应该替换老的value。事实上,它确实是这么做的。在迭代的过程中,会调用equals()方法来检查key的相等性(key.equals(k)),如果这个方法返回true,它就会用当前Entry的value来替换之前的value。

Get:

现在我们来看下get方法的实现:

  1. /**
  2. * Returns the value to which the specified key is mapped, or {@code null}
  3. * if this map contains no mapping for the key.
  4. *
  5. * <p>
  6. * More formally, if this map contains a mapping from a key {@code k} to a
  7. * value {@code v} such that {@code (key==null ? k==null :
  8. * key.equals(k))}, then this method returns {@code v}; otherwise it returns
  9. * {@code null}. (There can be at most one such mapping.)
  10. *
  11. * </p>
  12. * <p>
  13. * A return value of {@code null} does not <i>necessarily</i> indicate that
  14. * the map contains no mapping for the key; it's also possible that the map
  15. * explicitly maps the key to {@code null}. The {@link #containsKey
  16. * containsKey} operation may be used to distinguish these two cases.
  17. *
  18. * @see #put(Object, Object)
  19. */
  20. public V get(Object key) {
  21. if (key == null)
  22. return getForNullKey();
  23. int hash = hash(key.hashCode());
  24. for (Entry<k, V> e = table[indexFor(hash, table.length)]; e != null; e = e.next) {
  25. Object k;
  26. if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
  27. return e.value;
  28. }
  29. return null;
  30. }

当你理解了hashmap的put的工作原理,理解get的工作原理就非常简单了。

当你传递一个key从hashmap总获取value的时候:

1.对key进行null检查。如果key是null,table[0]这个位置的元素将被返回。

2.key的hashcode()方法被调用,然后计算hash值。

3.indexFor(hash,table.length)用来计算要获取的Entry对象在table数组中的精确的位置,使用刚才计算的hash值。

4.在获取了table数组的索引之后,会迭代链表,调用equals()方法检查key的相等性,如果equals()方法返回true,get方法返回Entry对象的value,否则,返回null。

要牢记以下关键点:

  • HashMap有一个叫做Entry的内部类,它用来存储key-value对。
  • 上面的Entry对象是存储在一个叫做table的Entry数组中。
  • table的索引在逻辑上叫做“桶”(bucket),它存储了链表的第一个元素。
  • key的hashcode()方法用来找到Entry对象所在的桶。
  • 如果两个key有相同的hash值,他们会被放在table数组的同一个桶里面。
  • key的equals()方法用来确保key的唯一性。
  • value对象的equals()和hashcode()方法根本一点用也没有。

原文链接: javacodegeeks    翻译: ImportNew.com - miracle1919
译文链接: http://www.importnew.com/10620.html

Java HashMap的工作原理的更多相关文章

  1. Java HashMap的工作原理(转载)

    原文地址:http://www.importnew.com/10620.html 面试的时候经常会遇见诸如:"java中的HashMap是怎么工作的","HashMap的 ...

  2. 【转】Java学习---HashMap的工作原理

    [原文]https://www.toutiao.com/i6592560649652404744/ HashMap的工作原理是近年来常见的Java面试题.几乎每个Java程序员都知道HashMap,都 ...

  3. Java中的HashMap的工作原理是什么?

    问答题23 /120 Java中的HashMap的工作原理是什么? 参考答案 Java中的HashMap是以键值对(key-value)的形式存储元素的.HashMap需要一个hash函数,它使用ha ...

  4. Java中的数据结构有哪些?HashMap的工作原理是什么?

    Java中常用数据结构 常用的数据结构有哈希表,线性表,链表,java.util包中有三个重要的接口:List,Set,Map常用来实现基本的数据结构 HashMap的工作原理 HashMap基于ha ...

  5. HashMap的工作原理

    HashMap的工作原理   HashMap的工作原理是近年来常见的Java面试题.几乎每个Java程序员都知道HashMap,都知道哪里要用HashMap,知道HashTable和HashMap之间 ...

  6. HashMap的工作原理深入再深入

    前言 首先再次强调hashcode (==)和equals的真正含义(我记得以前有人会说,equals是判断对象内容,hashcode是判断是否相等之类): equals:是否同一个对象实例.注意,是 ...

  7. [转] HashMap的工作原理

    HashMap的工作原理是近年来常见的Java面试题.几乎每个Java程序员都知道HashMap,都知道哪里要用HashMap,知道Hashtable和HashMap之间的区别,那么为何这道面试题如此 ...

  8. 【转】HashMap的工作原理

    很好的文章,推荐Java的一个好网站:ImportNew HashMap的工作原理是近年来常见的Java面试题.几乎每个Java程序员都知道HashMap,都知道哪里要用HashMap,知道Hasht ...

  9. 转:HashMap的工作原理,及笔记

    HashMap的工作原理是近年来常见的Java面试题.几乎每个Java程序员都知道HashMap,都知道哪里要用HashMap,知道Hashtable和HashMap之间的区别,那么为何这道面试题如此 ...

随机推荐

  1. Bean Life Cycle

    Bean生命周期 Spring Bean Life Cycle https://www.tutorialspoint.com/spring/spring_bean_life_cycle.htm The ...

  2. .m2\repository\org\springframework\spring-beans\4.1.4.RELEASE\spring-beans-4.1.4.RELEASE.jar!\org\springframework\beans\factory\xml\spring-beans-4.1.xsd

    <?xml version="1.0" encoding="UTF-8" standalone="no"?> <xsd:s ...

  3. 【云安全与同态加密_调研分析(7)】安全技术在云计算中的安全应用分析——By Me

                                                                   我司安全技术在云计算中的安全应用分析 1. 基于云计算参考模型,分析我司安 ...

  4. SQL Server 不同网段IP通过名称访问

    1, 设置订阅服务器C:\Windows\System32\drivers\etc目录的host文件,添加分发服务器(我的环境是发布服务器与分发服务器 是一起的,所以这里指定的是发布服务器的地址)信息 ...

  5. EasyUI Progressbar 进度条

    通过 $.fn.progressbar.defaults 重写默认的 defaults. 进度条(progressbar)提供了一种显示长时间操作进度的反馈.进度可被更新以便让用户知道当前正在执行的操 ...

  6. Virtualbox中win7虚拟机中U盘不可用问题的解决

    Virtualbox版本是5.0.0,主机运行多是Ubuntu12.04 LTS,虚拟机是Win7 X64.起初Win7正常运行,Virtualbox的增强功能已安装.下面是如何一步一步解决U盘不可用 ...

  7. [转] Delphi Socket Architecture

    Delphi Socket Architecture - Felix John COLIBRI. abstract : The architecture of the ScktComp socket  ...

  8. http webservice socket的区别

    1 数据传输方式1.1 socket传输的定义和其特点    所谓socket通常也称作"套接字",实现服务器和客户端之间的物理连接,并进行数据传输,主要有udp和tcp两个协议. ...

  9. requirements.txt

    在文件夹下 生成requirements.txt文件 pip freeze > requirements.txt 安装requirements.txt依赖 pip install -r requ ...

  10. UI自动化测试框架之Selenium关键字驱动

    一.原理及特点 1. 关键字驱动测试是数据驱动测试的一种改进类型 2. 主要关键字包括三类:被操作对象(Item).操作(Operation)和值(value),用面向对象形式可将其表现为Item.O ...