哈希表
hash表也称散列表(Hash table),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。
给定表M,存在函数f(key),对任意给定的关键字值key,代入函数后若能得到包含该关键字的记录在表中的地址,则称表M为哈希(Hash)表,函数f(key)为哈希(Hash) 函数。
简单理解就是:在记录的存储位置和它的关键字之间建立一个确定的对应关系f,使每个关键字和结构中一个唯一的存储位置相对应。
具有快速查找和插入操作的优点
hashcode
hashcode 通过hash函数计算得到,hashcode就是在hash表中有对应的位置
每个对象都有hashcode,通过将对象的物理地址转换为一个整数,将整数通过hash计算就可以得到hashcode
hashcode的作用
HashCode的存在主要是为了查找的快捷性,HashCode是用来在散列存储结构中确定对象的存储地址的
对于容器类设计 基本上都会涉及到hashCode。在Java中也一样,hashCode方法的主要作用是为了配合基于散列的集合一起正常运行,这样的散列集合包括HashSet、HashMap以及HashTable。
在对集合进行插入操作时,集合内时是不允许存在重复元素的,这样就引发了一个问题
如何判别在集合中是否已经存在该对象了?
首先想到的方法就是调用equals()方法,这个方法确实可行。但是如果集合中已经存在大量的数据或者更多的数据,如果采用equals方法去逐一比较,效率必然是一个问题。 此时hashCode方法的作用就体现出来了,当集合要添加新的对象时,先调用这个对象的hashCode方法,得到对应的hashcode值,实际上在HashMap的具体实现中会一个表保存已经存进去的对象的hashcode值,如果table中没有该hashcode值,它就可以直接存进去,不用再进行任何比较了;如果存在该hashcode值, 就调用它的equals方法与新元素进行比较,相同的话就不存了,不相同就散列其它的地址,所以这里存在一个冲突解决的问题,这样一来实际调用equals方法的次数就大大降低了。
这也就解释了为什么equals()相等,则hashCode()必须相等。如果两个对象equals()相等,则它们在哈希表(如HashSet、HashMap等)中只应该出现一次;如果hashCode()不相等,那么它们会被散列到哈希表的不同位置,哈希表中出现了不止一次。
hashCode 方法
Object类中hashCode()方法的声明如下:
publicnativeinthashCode();
可以看出,hashCode()是一个native方法,而且返回值类型是整形;实际上,该native方法将对象在内存中的地址作为哈希码返回,可以保证不同对象的返回值不同。
与equals()方法类似,hashCode()方法可以被重写。JDK中对hashCode()方法的作用,以及实现时的注意事项做了说明:
(1)hashCode()在哈希表中起作用,如java.util.HashMap。
(2)如果对象在equals()中使用的信息都没有改变,那么hashCode()值始终不变。
(3)如果两个对象使用equals()方法判断为相等,则hashCode()方法也应该相等。
(4)如果两个对象使用equals()方法判断为不相等,则不要求hashCode()也必须不相等;但是开发人员应该认识到,不相等的对象产生不相同的hashCode可以提高哈希表的性能。
重写hashcode()的原则
(1)如果重写了equals()方法,检查条件“两个对象使用equals()方法判断为相等,则hashCode()方法也应该相等”是否成立,如果不成立,则重写hashCode ()方法。
(2)hashCode()方法不能太过简单,否则哈希冲突过多。
(3)hashCode()方法不能太过复杂,否则计算复杂度过高,影响性能
如何计算 hashCode
1. 生成一个 int 类型的变量 result,并且初始化一个值,比如 17
2. 对类中每一个重要字段,也就是影响对象的值的字段,也就是 equals 方法里有比较的字段,进行以下操作:
a. 计算这个字段的值 filedHashValue
b. 执行 result = 31 * result + filedHashValue; 更新结果
而要如和计算这个字段的值 filedHashValue 值呢,根据字段类型分为三种情况,一种是基础数据(比如 int,boolean,),一种是对象,还有一种是数组。
如何计算每个重要字段的值
如果字段是基础数据,假设数值为 f, 根据类型
1. boolean,true 返回 1,false 返回 0
2. byte, char, short, int 返回对应的 int 值
3. long, 返回 f^(f>>>32)
4. float,返回 Float.floatToIntBits(f)
5. double,执行 Double.doubleToLongBits(f)转为 long, 再返回 compute(int)(f^(f>>>32))
而如果字段是对象,如果是 null,返回 0,否则根据上述【如何计算 hashCode】中所述步骤 1 和 2,计算这个对象的 hashCode,作为这个字段的值。实际上是一个递归的过程。
而如果字段是数组,则对每一个元素进行计算,得到 filedHashValue,并执行 result = 31 * result + filedHashValue;
这里补充一下 HashMap 对象是如何计算哈希值的,JDK 中,HashMap 类自身已经实现了 hashCode 方法。一个 hashMap 其实是 entry 的数组。
AbstractMap 中,hashCode 对数组中的每一个 entry 计算哈希值,并得到所有哈希值的和。