• 【原创】关于hashcode和equals的不同实现对HashMap和HashSet集合类的影响的探究


    这篇文章做了一个很好的测试:http://blog.csdn.net/afgasdg/article/details/6889383,判断往HashSet(不允许元素重复)里塞对象时,是如何判定set里的对象是否一致的问题。但是他是从写测试例子入手来看的,或许全文看下来还迷迷蒙蒙,印象不够深刻。

    所以看了这篇博文,我又去看了下HashSet的源码,在添加新元素时,调用的方法如下:

        public boolean add(E e) {
            return map.put(e, PRESENT)==null;
        }

    这说明,HashSet里存储对象最终还是利用了HashMap来存,所以如何判定对象是否一致的问题还要落在HashMap上。看看HashMap的put方法实现:

     public V put(K key, V value) {
            if (table == EMPTY_TABLE) {
                inflateTable(threshold);
            }
            if (key == null)
                return putForNullKey(value);
            int hash = hash(key);
            int i = indexFor(hash, table.length);
            for (Entry<K,V> 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;
        }

    其中最关键的几行,

      int hash = hash(key);

    取得传入的key的hash值,当然这里的key就是我们需要存储如HashSet的对象啦。

    那是如何取得hash值的呢?看看hash方法是如何实现的:

    final int hash(Object k) {
            int h = hashSeed;
            if (0 != h && k instanceof String) {
                return sun.misc.Hashing.stringHash32((String) k);
            }
    
            h ^= k.hashCode();
    
            // This function ensures that hashCodes that differ only by
            // constant multiples at each bit position have a bounded
            // number of collisions (approximately 8 at default load factor).
            h ^= (h >>> 20) ^ (h >>> 12);
            return h ^ (h >>> 7) ^ (h >>> 4);
        }

    对于非String对象,采取的是调用key自身实现的hashcode方法,并做一些异或操作,减少冲突,使得存储到HashMap时相对分散,提高效率。

    返回到put方法上的第二个关键代码行:

    int i = indexFor(hash, table.length);

    这一行代码是取得hash值对应的index位置,取得之后,就开始在对应链表上查找对象了(顺便说下HashMap的存储结果,它是一个数组和链表的组合体,由hashcode定位到数组的对应位置,然后将对象存储到这个位置指向的链表中,http://www.cnblogs.com/highriver/archive/2011/08/15/2139462.html文章说的很清楚,可以参考阅读)。在链表中查找对象的代码如下:

    for (Entry<K,V> 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;
                }
            }

    那么重点来了,看这一行:

    if (e.hash == hash && ((k = e.key) == key || key.equals(k)))

    所以这里我们可以得出结论:在往HashSet里add对象或者往HashMap里add对象(看key是否相同)时,比较对象(HashMap中是对其key比较)是否相同时,先看hashcode是否一样,不一样则表示一定不一样,可以添加;否则就表示是不相同的key(对hashset来说就是不同对象,可以同时保存在set中);如果hashcode相同,再比较对象地址是否一样或者两个对象是否equals,有一个为true则表示是相同的。

    相同则更新value值,这个我们不必关心,因为hashset没有value值,或者说value值都是PRESENT,而PRESENT是一个东东:

    private static final Object PRESENT = new Object();

    看到这里,差不多明白了HashSet是如何比较存入对象是否相同的了。那又该反思下自定义的覆盖的hashcode方法和equals方法实现了

    要使用HashSet和HashMap这些集合类进行存储,必须覆盖hashcode方法和equals方法实现,不然调用的都是Object的默认实现,而Object的默认实现是这样的:

    equals默认实现:

    public boolean equals(Object obj) {
            return (this == obj);
        }

    这里实现默认是对比对象的物理地址,如果你要改变这种equal方式,需要自己覆盖重写。看HashMap中对比一个key是否存在中&&后面的

    (k = e.key) == key || key.equals(k)

    只要一个成立则可以,所以equals的默认实现再这里就没有意义了,其表达的内在含义就在于:只要你覆盖了Object的equals默认实现,就只看你自己的equals 来绝对是否相等了(因为一般物理地址相等的不可能不equals)。

    再来看看hashcode的实现:

    /**
         * Returns a hash code value for the object. This method is
         * supported for the benefit of hash tables such as those provided by
         * {@link java.util.HashMap}.
         * <p>
         * The general contract of {@code hashCode} is:
         * <ul>
         * <li>Whenever it is invoked on the same object more than once during
         *     an execution of a Java application, the {@code hashCode} method
         *     must consistently return the same integer, provided no information
         *     used in {@code equals} comparisons on the object is modified.
         *     This integer need not remain consistent from one execution of an
         *     application to another execution of the same application.
         * <li>If two objects are equal according to the {@code equals(Object)}
         *     method, then calling the {@code hashCode} method on each of
         *     the two objects must produce the same integer result.
         * <li>It is <em>not</em> required that if two objects are unequal
         *     according to the {@link java.lang.Object#equals(java.lang.Object)}
         *     method, then calling the {@code hashCode} method on each of the
         *     two objects must produce distinct integer results.  However, the
         *     programmer should be aware that producing distinct integer results
         *     for unequal objects may improve the performance of hash tables.
         * </ul>
         * <p>
         * As much as is reasonably practical, the hashCode method defined by
         * class {@code Object} does return distinct integers for distinct
         * objects. (This is typically implemented by converting the internal
         * address of the object into an integer, but this implementation
         * technique is not required by the
         * Java<font size="-2"><sup>TM</sup></font> programming language.)
         *
         * @return  a hash code value for this object.
         * @see     java.lang.Object#equals(java.lang.Object)
         * @see     java.lang.System#identityHashCode
         */
        public native int hashCode();

    也许你看到会很奇怪,为啥没有实现呢?都是祖先类了。这也是我为什么贴注释的原因了。

    native关键字所定义的方法的实现不在java源码范围内,它是java与其他语言协作运行所定义的一种方法。

    它是操作系统基本的实现,一般应该是用C/C++实现的的原生方法,并且被编译为DLL,由Java去调用。我们知道Java是一个跨平台的语言,所以它只能牺牲这些底层的控制,根据不同平台的具体实现来调用。

  • 相关阅读:
    iOS开发 -------- AFNetworking使用中遇到的小问题
    iOS开发 -------- AFNetworking实现简单的断点下载
    规则引擎-BRMS在企业开发中的应用
    MySQL系列教程(四)
    MySQL系列教程(三)
    MySQL系列教程(二)
    MySQL系列教程(一)
    OWASP Top 10十大风险 – 10个最重大的Web应用风险与攻防
    【事务】<查询不到同一调用方法其它事务提交的更新>解决方案
    iOS控制反转(IoC)与依赖注入(DI)的实现
  • 原文地址:https://www.cnblogs.com/gslyyq/p/4255813.html
Copyright © 2020-2023  润新知