数据结构基础10:哈希表常见问题

一、有两个字典,分别存有 100 条数据和 10000 条数据,如果用一个不存在的 key 去查找数据,在哪个字典中速度更快?

有些计算机常识的读者都会立刻回答: “一样快,底层都用了哈希表,查找的时间复杂度为 O(1)”。然而实际情况真的是这样么?

答案是否定的,存在少部分情况两者速度不一致。

答:在 Java 和 Objective-C 中,如果哈希函数不合理,返回值过于集中,会导致大字典更慢。

Java 由于存在链表和红黑树互换机制,搜索时间呈对数级增长,而非线性增长。在理想的哈希函数下,无论字典多大,搜索速度都是一样快。

二、可以直接根据hashcode值判断两个Java对象是否相等吗?

Java中的hashCode()方法就是根据一定的规则将与对象相关的信息(比如对象的地址或对象的字段等)映射成一个数值,这个数值称作为hashCode(散列值)。

答案:肯定是不可以的,因为不同的对象可能会生成相同的hashCode。

虽然不能根据hashcode值判断两个对象是否相等,但是可以直接根据hashcode值判断两个对象不等,如果两个对象的hashcode值不等,则必定是两个不同的对象。如果要判断两个对象是否真正相等,必须通过equals方法。

三、hashCode()方法的作用

1、对于包含容器类型的程序设计语言来说,基本上都会涉及到hashCode。在Java中也一样,hashCode方法的主要作用是为了提高基于散列的集合的搜索效率,这样的散列集合包括HashSet、HashMap以及HashTable。

为什么这么说呢?考虑一种情况,当向集合中插入对象时,如何判别在集合中是否已经存在该对象了?(注意:集合中不允许重复的元素存在)。也许大多数人都会想到调用equals方法来逐个进行比较,这个方法确实可行。但是如果集合中已经存在一万条数据或者更多的数据,如果采用equals方法去逐一比较,效率必然是一个问题。

此时hashCode方法的作用就体现出来了,当集合要添加新的对象时,先调用这个对象的hashCode方法,得到对应的hashcode值,实际上在HashMap的具体实现中会用一个table保存已经存进去的对象的hashcode值,如果table中没有该hashcode值,它就可以直接存进去,不用再进行任何比较了;如果存在该hashcode值, 就调用它的equals方法与新元素进行比较,相同的话就不存了,不相同就散列其它的地址,所以这里还存在一个冲突解决的问题,这样一来实际调用equals方法的次数就大大降低了,提高供了效率。

2、下面这段代码是java.util.HashMap的中put方法的具体实现:

public V put(K key, V value)
{
        if (key == null)
            return putForNullKey(value);

        int hash = hash(key.hashCode());
        int i = indexFor(hash, table.length);
        for (Entry e = table[i]; e != null; e = e.next) 
       {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
 
        modCount++;
        addEntry(hash, key, value, i);
        return null;
 }

put方法是用来向HashMap中添加新的元素,从put方法的具体实现可知,会先调用hashCode方法得到该元素的hashCode值,然后查看table中是否存在该hashCode值,如果存在则调用equals方法重新确定是否存在该元素,如果存在,则更新value值,否则将新的元素添加到HashMap中。从这里可以看出,hashCode方法的存在是为了减少equals方法的调用次数,从而提高程序效率。

四、重写equals方法的同时,为什么必须重写hashCode方法?

在有些情况下,程序员在设计一个类的时候需要重写equals方法,比如String类。但是千万要注意,在重写equals方法的同时,必须重写hashCode()方法。

Object的hashCode()默认根据对象的内存地址,利用哈希算法求得hashCode。

数据结构基础10:哈希表常见问题_第1张图片

 

原因:

1、如果只重写了equals方法而没有重写hashCode方法的话,则会违反约定的第二条:相等的对象必须具有相同的hashCode。

例如:现在有两个Student对象,已经重写equals方法(比较其属性是否相等)。

    Student s1=new Student("小明",18);

    Student s2=new Student("小明",18);

    此时s1.equals(s2)返回true。

假如只重写equals而不重写hashcode,那么Student类的hashcode方法就是Object默认的hashcode方法,由于默认的hashcode方法是根据对象的内存地址经哈希算法得来的,显然此时s1!=s2,故两者的hashcode不一定相等。

然而重写了equals,且s1.equals(s2)返回true,根据hashcode的规则,两个对象相等其哈希值一定相等,所以矛盾就产生了,因此重写equals一定要重写hashcode。

2、同时对于HashSet和HashMap这些基于散列值实现的类来说,hashcode是其正常运作的关键。

HashMap的底层处理机制是以数组的方法保存放入的数据的(Node[] table),其中的关键是数组下标的处理。数组的下标是根据传入的元素hashCode方法的返回值再和特定的值异或决定的。
如果该数组位置上已经有放入的值了,且传入的键值相等则不处理,若不相等则覆盖原来的值,如果数组位置没有条目,则插入,并加入到相应的链表中。

检查键是否存在也是根据hashCode值来确定的。要检查的键代表的对象和已经插入的键对象的内存地址不同,如果不重写hashCode()的话,就会求出不同的hashCode从而会让HashMap出现相同元素,最终导致HashSet、HashMap不能正常的运作。

 

 

 

你可能感兴趣的:(数据结构与算法)