Java中的equals和hashCode方法详解(三)

2014-11-23 23:56:37 · 作者: · 浏览: 6
r3 = new RectObject(3,3); set.add(r1); set.add(r2); set.add(r3); r3.y = 7; System.out.println("删除前的大小size:"+set.size()); set.remove(r3); System.out.println("删除后的大小size:"+set.size()); } }

运行结果:

删除前的大小size:3
删除后的大小size:3

擦,发现一个问题了,而且是个大问题呀,我们调用了remove删除r3对象,以为删除了r3,但事实上并没有删除,这就叫做内存泄露,就是不用的对象但是他还在内存中。所以我们多次这样操作之后,内存就爆了。看一下remove的源码:

/**
     * Removes the specified element from this set if it is present.
     * More formally, removes an element e such that
     * (o==null   e==null : o.equals(e)),
     * if this set contains such an element.  Returns true if
     * this set contained the element (or equivalently, if this set
     * changed as a result of the call).  (This set will not contain the
     * element once the call returns.)
     *
     * @param o object to be removed from this set, if present
     * @return true if the set contained the specified element
     */
    public boolean remove(Object o) {
        return map.remove(o)==PRESENT;
    }

然后再看一下remove方法的源码:

/**
     * Removes the mapping for the specified key from this map if present.
     *
     * @param  key key whose mapping is to be removed from the map
     * @return the previous value associated with key, or
     *         null if there was no mapping for key.
     *         (A null return can also indicate that the map
     *         previously associated null with key.)
     */
    public V remove(Object key) {
        Entry
  
    e = removeEntryForKey(key);
        return (e == null   null : e.value);
    }
  

在看一下removeEntryForKey方法源码:

/**
     * Removes and returns the entry associated with the specified key
     * in the HashMap.  Returns null if the HashMap contains no mapping
     * for this key.
     */
    final Entry
  
    removeEntryForKey(Object key) {
        int hash = (key == null)   0 : hash(key);
        int i = indexFor(hash, table.length);
        Entry
   
     prev = table[i]; Entry
    
      e = prev; while (e != null) { Entry
     
       next = e.next; Object k; if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) { modCount++; size--; if (prev == e) table[i] = next; else prev.next = next; e.recordRemoval(this); return e; } prev = e; e = next; } return e; }
     
    
   
  

我们看到,在调用remove方法的时候,会先使用对象的hashCode值去找到这个对象,然后进行删除,这种问题就是因为我们在修改了r3对象的y属性的值,又因为RectObject对象的hashCode方法中有y值参与运算,所以r3对象的hashCode就发生改变了,所以remove方法中并没有找到r3了,所以删除失败。即r3的hashCode变了,但是他存储的位置没有更新,仍然在原来的位置上,所以当我们用他的新的hashCode去找肯定是找不到了。

其实上面的方法实现很简单的:如下图:

\

很简单的一个线性的hash表,使用的hash函数是mod,源码如下:

 /**
     * Returns index for hash code h.
     */
    static int indexFor(int h, int length) {
        return h & (length-1);
    }
这个其实就是mod运算,只是这种运算比%运算要高效。

1,2,3,4,5表示是mod的结果,每个元素对应的是一个链表结构,所以说想删除一个Entry 的话,首先得到hashCode,从而获取到链表的头结点,然后再遍历这个链表,如果hashCode和equals相等就删除这个元素。

上面的这个内存泄露告诉我一个信息:如果我们将对象的属性值参与了hashCode的运算中,在进行删除的时候,就不能对其属性值进行修改,否则会出现严重的问题。


其实我们也可以看一下8种基本数据类型对应的对象类型和String类型的hashCode方法和equals方法。

其中8中基本类型的hashCode很简单就是直接返回他们的数值大小,String对象是通过一个复杂的计算方式,但是这种计算方式能够保证,如果这个字符串的值相等的话,他们的hashCode就是相等的。8种基本类型的equals方法就是直接比较数值,String类型的equals方法是比较字符串的值的。