哈希表中的“表”
当我们需要存储一种映射数据时,就需要用到哈希表,也称为散列表(Hash Table)。
该数据结构中存储的是键(key)和值(value),它们是对应关系。
只需要给出一个指定的key,那么就能高效查找到它所匹配的值,效率非常高,时间复杂度接近于O(1)。
这就是哈希表中的“表”,表中维护了key与value的映射关系。
哈希表中的“哈希”
思考下,那么该如何存储键值对(key—value)呢?
如果使用链式存储结构:
肯定是不行的,因为如果要查找到指定key对应的结点,就需要遍历整个链表,时间复杂度很大,不是O(1)。
考虑使用顺序存储结构:
即使用数组,但数组只能根据下标,像a[0]、a[1]、a[2]这样来访问,但哈希表的key通常不是整数型,而多是字符串类型。
像a["key1"]、a["key2"]这样访问是无法的,语法不允许。
所以考虑通过一个中介,将key和数组下标进行转换,也是一种映射,来达到访问对应值value的目的,而具有这种转换能力的就叫做哈希函数。
Hash,一般翻译做散列、杂凑,或音译为哈希,是把任意长度的输入(又叫做预映射pre-image)通过散列算法变换成固定长度的输出,该输出就是散列值。
这就是哈希表中的“哈希"。
在JDK1.8之前中HashMap使用的数组+链表结构。
在JDK1.8及之后中HashMap使用的数组+链表+红黑树结构。
哈希冲突
哈希冲突是发生在哈希表中插入新的键值对的时候。
像哈希表中添加键值对的步骤如下:
第一步:通过哈希函数,将key转换成数组下标。
第二步:如果数组下标位置没有元素,即为null,那么把该键值对添加到该位置。(在HashMap中使用Entry对象表示键值对)
但注意,数组的长度是有限的,当添加的元素越来越多,超过数组长度的时候,不同的key通过哈希函数获得的下标可能是相同的,但是在下标位置已经有元素了,那么这就发生了哈希冲突。
解决哈希冲突的方法有很多种,如开放寻址法、链表法(拉链法)。
在HashMap中就使用的是拉链法解决哈希冲突。
HashMap数组中每个元素都是一个Entry,同时是一个链表的头结点,Entry不仅保存了键和值
还有next指针,指向下一个结点。
源码HashMap中的putVal方法有所说明: