Java HashMap的工作原理
面试的时候经常会遇见诸如:”java中的HashMap是怎么工作的”、”HashMap的get和put内部的工作原理”这样的问题。
本文将用一个简单的例子来解释下HashMap内部的工作原理。
首先我们从一个例子开始,而不仅仅是从理论上,这样,有助于更好地理解,然后,我们来看下get和put到底是怎样工作的。
我们来看个非常简单的例子。
有一个”国家”(Country)类,我们将要用Country对象作为key,它的首都的名字(String类型)作为value。
下面的例子有助于我们理解key-value对在HashMap中是如何存储的。
1. Country.java
public class Country {
String name;
long population;
public Country(String name, long population) {
super();
this.name = name;
this.population = population;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public long getPopulation() {
return population;
}
public void setPopulation(long population) {
this.population = population;
}
// If length of name in country object is even then return 31(any random
// number) and if odd then return 95(any random number).
// This is not a good practice to generate hashcode as below method but I am
// doing so to give better and easy understanding of hashmap.
@Override
public int hashCode() {
if (this.name.length() % 2 == 0)
return 31;
else
return 95;
}
@Override
public boolean equals(Object obj) {
Country other = (Country) obj;
if (name.equalsIgnoreCase((other.name)))
return true;
return false;
}
}
2. HashMapStructure.java(main class)
import java.util.HashMap;
import java.util.Iterator; public class HashMapStructure {
/**
* @author Arpit Mandliya
*/
public static void main(String[] args) { Country india = new Country("India", 1000);
Country japan = new Country("Japan", 10000);
Country france = new Country("France", 2000);
Country russia = new Country("Russia", 20000); HashMap<Country, String> countryCapitalMap = new HashMap<Country, String>();
countryCapitalMap.put(india, "Delhi");
countryCapitalMap.put(japan, "Tokyo");
countryCapitalMap.put(france, "Paris");
countryCapitalMap.put(russia, "Moscow"); Iterator<Country> countryCapitalIter = countryCapitalMap.keySet().iterator();// put debug point at this line
while (countryCapitalIter.hasNext()) {
Country countryObj = countryCapitalIter.next();
String capital = countryCapitalMap.get(countryObj);
System.out.println(countryObj.getName() + "----" + capital);
}
} }
现在,在第21行设置一个断点,在项目上右击->调试运行(debug as)->java应用(java application)。程序会停在23行,然后在countryCapitalMap上右击,选择“查看”(watch)。将会看到如下的结构:

从上图可以观察到以下几点:
1.有一个叫做table大小是16的Entry数组。
2.这个table数组存储了Entry类的对象。HashMap类有一个叫做Entry的内部类。这个Entry类包含了key-value作为实例变量。
我们来看下Entry类的结构。Entry类的结构:
static class Entry implements Map.Entry
{
final K key;
V value;
Entry next;
final int hash;
...//More code goes here
}
每当往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方法的实现:
/**
* Associates the specified value with the specified key in this map. If the
* map previously contained a mapping for the key, the old value is
* replaced.
*
* @param key
* key with which the specified value is to be associated
* @param value
* value to be associated with the specified key
* @return the previous value associated with <tt>key</tt>, or <tt>null</tt>
* if there was no mapping for <tt>key</tt>. (A <tt>null</tt> return
* can also indicate that the map previously associated
* <tt>null</tt> with <tt>key</tt>.)
*/
public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
int hash = hash(key.hashCode());
int i = indexFor(hash, table.length);
for (Entry<k, V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
} modCount++;
addEntry(hash, key, value, i);
return null;
}
现在我们一步一步来看下上面的代码。
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方法的实现:
/**
* Returns the value to which the specified key is mapped, or {@code null}
* if this map contains no mapping for the key.
*
* <p>
* More formally, if this map contains a mapping from a key {@code k} to a
* value {@code v} such that {@code (key==null ? k==null :
* key.equals(k))}, then this method returns {@code v}; otherwise it returns
* {@code null}. (There can be at most one such mapping.)
*
* </p>
* <p>
* A return value of {@code null} does not <i>necessarily</i> indicate that
* the map contains no mapping for the key; it's also possible that the map
* explicitly maps the key to {@code null}. The {@link #containsKey
* containsKey} operation may be used to distinguish these two cases.
*
* @see #put(Object, Object)
*/
public V get(Object key) {
if (key == null)
return getForNullKey();
int hash = hash(key.hashCode());
for (Entry<k, V> e = table[indexFor(hash, table.length)]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
return e.value;
}
return null;
}
当你理解了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的工作原理的更多相关文章
- Java HashMap的工作原理(转载)
原文地址:http://www.importnew.com/10620.html 面试的时候经常会遇见诸如:"java中的HashMap是怎么工作的","HashMap的 ...
- 【转】Java学习---HashMap的工作原理
[原文]https://www.toutiao.com/i6592560649652404744/ HashMap的工作原理是近年来常见的Java面试题.几乎每个Java程序员都知道HashMap,都 ...
- Java中的HashMap的工作原理是什么?
问答题23 /120 Java中的HashMap的工作原理是什么? 参考答案 Java中的HashMap是以键值对(key-value)的形式存储元素的.HashMap需要一个hash函数,它使用ha ...
- Java中的数据结构有哪些?HashMap的工作原理是什么?
Java中常用数据结构 常用的数据结构有哈希表,线性表,链表,java.util包中有三个重要的接口:List,Set,Map常用来实现基本的数据结构 HashMap的工作原理 HashMap基于ha ...
- HashMap的工作原理
HashMap的工作原理 HashMap的工作原理是近年来常见的Java面试题.几乎每个Java程序员都知道HashMap,都知道哪里要用HashMap,知道HashTable和HashMap之间 ...
- HashMap的工作原理深入再深入
前言 首先再次强调hashcode (==)和equals的真正含义(我记得以前有人会说,equals是判断对象内容,hashcode是判断是否相等之类): equals:是否同一个对象实例.注意,是 ...
- [转] HashMap的工作原理
HashMap的工作原理是近年来常见的Java面试题.几乎每个Java程序员都知道HashMap,都知道哪里要用HashMap,知道Hashtable和HashMap之间的区别,那么为何这道面试题如此 ...
- 【转】HashMap的工作原理
很好的文章,推荐Java的一个好网站:ImportNew HashMap的工作原理是近年来常见的Java面试题.几乎每个Java程序员都知道HashMap,都知道哪里要用HashMap,知道Hasht ...
- 转:HashMap的工作原理,及笔记
HashMap的工作原理是近年来常见的Java面试题.几乎每个Java程序员都知道HashMap,都知道哪里要用HashMap,知道Hashtable和HashMap之间的区别,那么为何这道面试题如此 ...
随机推荐
- HDU_2586_How far away ?
How far away ? Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)To ...
- Tensorflow%20实战Google深度学习框架 4.2.2 自定义损失函数源代码
import os import tab import tensorflow as tf from numpy.random import RandomState print "hello ...
- (ubuntu ufw)My firewall is blocking network connections from the docker container to outside
Maybe this is due to the current version, but the current answer doesn't work on my system (Docker 0 ...
- Wireshark分析之TCP协议(二)
(1)TCP首部格式 源端口: 用来传输数据报的端口 目标端口: 数据包将要发送到的端口 序号: 用来表示一个TCP片段.这个值用来表示数据流中的部分数据没有丢失 确认号: 表示通信中希望从另一 ...
- (2.3)DDL增强功能-流程化控制与动态sql
1.流程控制 在T-SQL中,与流程控制语句相关的关键字有8个: BEGIN...END BREAK GOTO CONTINUE IF...ELSE WHILE RETURN WAITFOR 其实还可 ...
- mysql 数据操作 单表查询 练习
查出所有员工的名字,薪资,格式为 <名字:egon>,<薪资:3000> mysql> select concat('<姓名:',name,'>') as n ...
- ARM上电启动及Uboot代码分析
版权声明:本文为博主原创文章,未经博主同意不得转载. https://blog.csdn.net/qianlong4526888/article/details/27698707 注意:由于文档是去年 ...
- [golang grpc] 框架介绍
官方网站 http://www.grpc.io/ http://www.grpc.io/docs/quickstart/go.html grpc安装 • go安装 目前grpc需要go 1.5以上版本 ...
- with语句与__enter__,__exit__
class Foo(object): def func(self): print("func") pass def __enter__(self): print("ent ...
- map.containsKey
该方法判断Map集合对象中是否包含指定的键名.如果Map集合中包含指定的键名,则返回true,否则返回false. 语法 containsKey(Object key) . e.g public s ...