1.概述
Map<k,v="">是常见的键值对存储接口,Java中存储键值对的数据类型都实现了这个接口,表示映射表。其中有两个核心操作get(Object key)和put(K key, V value),分别用来获取键对应的值以及向映射表中插入键值对。
public interface Map<K,V> {
···
V get(Object key);
V put(K key, V value);
···
}
HashMap是Map的常见实现类,也是Java中使用最频繁的储存键值对的结构,这篇文章我们就来看一下它的基本原理。
2.HashMap的工作原理
Java中的数组在添加或者删除的时候,都会复制一个新数组,比较耗内存,但是数组遍历比较高效(寻址容易,插入和删除困难);然而,链表则是遍历比较慢,而添加和删除元素代价低(寻址困难,插入和删除容易)。HashMap则是巧妙的结合这两点——哈希表。
HashMap是一个用于存储Key-Value键值对的集合,每一个键值对也叫做Entry。这些键值对(Entry)分散存储在一个数组中,这个数组就是HashMap的主干。
从这张图可以看出来,HashMap是数组+链表实现的。在它的主干数组上,每一个元素存储的不仅是一个Entry对象,也是一个链表的头节点。每一个Entry通过Next指针指向它所在链表的下一个Entry节点。
2.1-put
一个HashMap默认的长度为16(数组长度)。当我们调用HashMap的put方法,向其中放入一个Entry(键值对)时,有这样一个问题要解决:这个Entry对象存储在HashMap中哪个位置?通过下面的算法来决定:
int hash = hash(key);
int i = indexFor(hash,table.length);
假如我们向HashMap中put(100,haozz)这个元素,那么首先需要计算100这个key的hash值,然后indexFor方法对该哈希值和HashMap数组的长度进行运算(实际上是与运算)。得到的值即是该元素存储在该HashMap中的下标值。
这样,我们就明确了新来的Entry对象存储在HashMap中的位置。那么新的问题又来了:如果我有两个Entry,他们通过上面的运算之后得到的下标相同,那该怎么办呢?这个时候就该链表登场了。我们之前说过,每一个Entry有一个Next属性指向下一个Entry。当新来的Entry对象(Entry-A)映射到的下标值为2,而且2位置上已经有对象(Entry-B)的时候,它只需要插入到对应的链表即可。