设计背景
在数组中,我们可以根据索引快速取出某个位置元素的值(时间复杂度为O(1)级别),但是数组中每一个元素的索引是依据添加顺序决定的,该索引与元素本身也没有任何关联,当我们想再次查找某个元素的时候,只能将数组遍历一次才能找到我们需要的元素,相当的耗时(时间复杂度为O(n)级别)
树结构的诞生在很大程度上加快了搜索速度(例如二分搜索树在理想状态下的时间复杂度为O(logn)级别),但单从速度上讲依旧不及数组的索引取值。
我们希望设计一个函数,这个函数能通过每一个元素的特征来生成对应的索引值,然后将这个元素按它的索引值存进数组中,当我们需要从数组中取出该元素时,只需要利用这个函数再次计算该元素的索引值,这样就能快速的定位到该元素在数组中的位置了,在这种方案下设计的线性表就是著名的哈希表(Hash Table),而这种函数就被称为哈希函数(Hash Function),所生成的值便是哈希值(Hash)。
结构分析
【结构类型】线性结构
【底层实现】数组
【核心方法】
public void add(K key, V value); //向哈希表中添加元素
public V remove(K key); //从哈希表中移除元素
private void resize(int newM); //扩容和缩容
哈希函数
哈希表看似简单粗暴,似有一种秒杀全场的感觉,但设计哈希表所要面临的问题也是异常复杂的。
首先,哈希函数需要精良设计以尽可能的让每一个元素的哈希值分布更均匀、更高效;其次,哈希表所存储的元素有各种各样的类型,每一种类型所适应的方案也都不同;更进一步说,哈希索引所带来的间接影响就是庞大的数组容量问题。哈希函数的设计还需要牵涉其它学科的知识。
本文将举一个最简化的例子来演示哈希函数大致的运行过程:
public class Person {
/**
* 实例域:编号、性别、名字
*/
int id;
String sex;
String name;
/**
* 构造器:对实例域进行初始化
*
* @param id
* @param sex
* @param name
*/
public Person(int id, String sex, String name) {
this.id = id;
this.sex = sex;
this.name = name;
}
/**
* 覆写方法:哈希函数
*
* @return 哈希值
*/
@Override
public int hashCode() {
// 定义因数
int B = 32;
// 定义哈希值变量
int hash = 0;
// 将类中的属性转换成整形并累加
hash = hash * B + ((Integer) id).hashCode();
hash = hash * B + sex.hashCode();
hash = hash * B + name.hashCode();
return hash;
}
public static void main(String[] args) {
Person person1 =<