哈希表也叫散列表,是一种非常重要的数据结构,应用场景及其丰富,许多缓存技术(比如:Redis)的核心其实就是在内存中维护一张大的哈希表,而HashMap的实现原理也常常出现在各类的面试题中,重要性可见一斑。
在讨论哈希表之前,我们先回顾一下数组和链表来实现对数据的存储的优缺点:
**数组:**占用空间连续。 寻址容易,查询速度快。但是,增加和删除效率非常低。
**链表:**占用空间不连续。 寻址困难,查询速度慢。但是,增加和删除效率非常高。
从上分析我们知道,数组优势是查询效率高,链表的优势是增删效率高。那么有没有一种数据结构能结合“数组+链表”的双方优点呢?答案就是“哈希表”。
哈希表的本质就是“数组+链表”,这是一种非常重要的数据结构。在哈希表中进行添加、删除和查找等操作,性能十分之高,不考虑哈希冲突的情况下,仅需一次定位即可完成。
我们知道,数据结构的物理存储结构只有两种:顺序存储结构和链式存储结构。而在上面我们提到过,在数组中根据下标查找某个元素,一次定位就可以达到,哈希表利用了这种特性,哈希表的主干就是数组。
我们打开HashMap源码,发现有如下两个核心内容:
其中的,Node[] table 就是HashMap的核心数组结构,我们也称之为“位桶数组”。我们再继续看Node是什么,源码如下:
一个Node对象存储了:
key:键对象
value:值对象
next:下一个节点
hash:键对象的hash值
显然就是一个单向链表结构,我们使用图形表示一个Entry的典型示意:
然后,我们画出Node[]数组的结构(这也是HashMap的结构):
由图可知,哈希表就是数组链表,底层还是数组但是这个数组每一项就是一个链表。
接下来我们来基于JDK1.7来模拟HashMap的实现,本章节重点模拟HashMap的put()方法和get()方法,在进行模拟put()方法和get()方法的实现之前,我们先做好相关的准备工作。
首先创建一个Node节点类,Node节点类是HashMap的内部类,它有几个重要的属性:键对象(key) 、值对象(value)、键对象的hash值(hash)和下一个节点(next)。
代码实现如下:
class MyHashMap<K, V> {
// Node节点,是一个单链表
static class Node<K, V> {
int hash; // 键对象的hash值
K key; // 键对象
V value; // 值对象
Node<K, V> next; // 下一个节点
// 构造方法
public Node(int hash, K key, V value, Node<K, V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
}
}
Node节点类实现完毕,我们继续添加HashMap中的两个重要属性:哈希表的Node[]数组(table)和存放元素的个数(size)属性。
代码实现如下: