在实现快速查询上,哈希表是经常使用的一种数据结构,是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。(底层为数据和链表的结合体)
给定表M,存在函数f(key),对任意给定的关键字值key,代入函数后若能得到包含该关键字的记录在表中的地址(HashCode),则称表M为哈希(Hash)表,函数f(key)为哈希(Hash) 函数。
Map和HashMap
Map<K, V>是一个以 键值(Key)-数值(Value) 对应形式存储数据的接口。 在数组中我们是通过数组下标来对其内容索引的,而在Map中我们通过对象来对对象进行索引,用来索引的对象叫做key,其对应的对象叫做value。
实现首先要导入类:
import java.util.Map;
import java.util.HashMap;
Key定义为String,Value定义为Integer。
Map<String, Integer> Ages = new HashMap<String, Integer>();
这样就用接口实现了定义哈希表。但是哈希表内部又是如何实现成了一个问题,下面说一下对哈希表的理解。
哈希表
哈希表如何实现直接查找的呢,举一个例子,假如我的数组A中,第i个元素里面装的key就是i,那么数字3肯定是在第3个位置,数字10肯定是在第10个位置。哈希表就是利用这种基本的思想,建立一个从key到位置的函数,然后进行直接计算查找。而查找时使用的哈希值使用哈希(Hash)算法算出,Hash主要用于信息安全领域中加密算法,它把一些不同长度的信息转化成杂乱的128位的编码,这些编码值叫做Hash值. 也可以说,Hash就是找到一种Key值和数据存放地址之间的映射关系。
整体方法就是将元素的特征变为数组的下标的方法,哈希表的每一个入口都由一个链表实现,在需要保存全部的字符串当存储记录时,通过Hash计算出记录的哈希值,当查找记录时,我们通过同样的是Hash计算记录的哈希值,并按此哈希值访问该记录。
虽然哈希表拥有近乎**O(1)**的时间复杂度但是,在存储大量的数据时,由于数组存储的限制会导致性能快速下降,并且还会出现不同的关键字经过散列函数的计算得到了相同的散列地址(哈希冲突)。
哈希冲突
在解决哈希冲突方面有多种方法:
一、 开放定址法
一旦发生冲突,就去寻找新的空的地址或者找到了相同的给定关键字,若未找到,则会失败。
二、双哈希法
使用多个Hash算法,在发生冲突后,使用后面的第二个,第三个…直到无冲突。
三、 链地址法
每个哈希表节点都有一个next指针,多个哈希表节点可以用next指针构成一个单向链表,被分配到同一个索引上的多个节点可以用这个单向 链表连接起来。此时即使产生冲突,但是可以通道next指针将冲突所在所在的节点连接起来,这样就解决了哈希的冲突问题。
四、 建立公共溢出区
即建立一个缓冲区,将冲突的放在缓冲区中,在查找不到时在缓冲区中查找。
哈希值
哈希值即通过Hash算法得到的哈希表的”地址值“,HashCode(哈希值)是所有Java对象的固有方法,如果不重载的话,返回的实际上是该对象在jvm的堆上的内存地址,而不同对象的内存地址肯定不同,所以这个HashCode也就肯定不同了。如果重载了的话,由于采用的算法的问题,有可能导致两个不同对象的HashCode相同。对于string类,只要字符串内容相同,返回的哈希码也相同。规范要求若重写equals(Object obj)方法,有必要重写hashcode()方法,确保通过equals(Object obj)方法判断结果为true的两个对象具备相等的hashcode()返回值,这样就确定了相同的值的对象的hashcode相同。但是,即使equals(Object obj)返回false,即两个对象不相同,也不要求调用hashcode()方法必须要得到两个不同的数。
所以得到如下推论:
1、如果两个对象equals,Java运行时环境会认为他们的hashcode一定相等。
2、如果两个对象不equals,他们的hashcode有可能相等。
3、如果两个对象hashcode相等,他们不一定equals。
4、如果两个对象hashcode不相等,他们一定不equals。