精华帖 (0) :: 良好帖 (2) :: 新手帖 (3) :: 隐藏帖 (9)
|
|
---|---|
作者 | 正文 |
发表时间:2011-02-15
最后修改:2011-02-15
改hashCode以后,在map中就找不到这个对象了。具体可参见源码,刚才理解错了,,源码中有个hash == key.hash这种情况的判断,旧的hash是会被存储的,改了hashCode新hash和旧hash应该不会一致吧。。。
|
|
返回顶楼 | |
发表时间:2011-02-15
哎,JDK编码格式可读性还真差
|
|
返回顶楼 | |
发表时间:2011-02-15
很好,我也要一起过一遍
|
|
返回顶楼 | |
发表时间:2011-02-15
最后修改:2011-02-15
是个好问题,不过前面分析的都不太正确,我来试着说明一下。
先看代码: //section 0 Entry的定义 final K key; V value; Entry<K,V> next; final int hash; <--Entry的hash永不会变 //section 1 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))) { <--用entry的hash在比较 V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } modCount++; addEntry(hash, key, value, i); <--在hash位置的链表中找不到entry时,添加 //section 2 void addEntry(int hash, K key, V value, int bucketIndex) { Entry<K,V> e = table[bucketIndex]; table[bucketIndex] = new Entry<K,V>(hash, key, value, e); <-- 传入链表head if (size++ >= threshold) resize(2 * table.length); } //section 3 Entry(int h, K k, V v, Entry<K,V> n) { value = v; next = n; <-- 把当前entry设为head,把原head设为head->next key = k; hash = h; } 问题原因: 之前当key分别为1和2时, 假设hashcode在优化后得到的table数组下标(这个下标是根据数据power of 2的length取低位得到的)不同,则分布在table的两个不同bucket上,当2变成1以后,Person2的对象hash值发生了变化,但是entry的hash值不会发生变化,数组位置也不会变,因为不存在listener。当进行get操作时,首先数组下标根据hash值会定位在1上,而2则无法被找到,只能通过数组遍历取得,存在潜在的内存泄露风险。 假设hash算法不好,两个对象落在同一个bucket上,则根据指针变化可知p2在p1之前被定位到,此时将一直返回p2,而p1则是潜在的内存泄露 |
|
返回顶楼 | |
发表时间:2011-02-16
最后修改:2011-02-16
楼上(yangyi)说的有点问题。最开始我和你的分析一样,而且连容积率、概率等都算进去了,但是后来发现了一个细节彻底推翻了我的推论。
我们先不讨论2个对象的情况,单独对该对象在hashMap中改变hashCode是否可以被查找进行分析: 如你所说 引用 final int hash; <--Entry的hash永不会变
而在get方法内有这样一句: int hash = hash(key.hashCode()); //此处省略若干 if (e.hash == hash && ((k = e.key) == key || key.equals(k))) return e.value; 即便 引用 假设hash算法不好,两个对象落在同一个bucket上
如果hash改变过,e.hash == hash永远不成立(认为改变hashcode方法以后hash一定不一样) 故,无论如何,只要改过hashCode,肯定查找不到。 借用你结论用一下,我做一点修改就应该是比较正确的答案了: 第一部分没变: 引用 之前当key分别为1和2时,
假设hashcode在优化后得到的table数组下标(这个下标是根据数据power of 2的length取低位得到的[我插一句,这个地方说复杂了,CAPACITY(散列表长度)一定是2的若干次方,那么这个地方是用hashCode对散列表长度取余以确定落在哪个bucket上])不同,则分布在table的两个不同bucket上,当2变成1以后,Person2的对象hash值发生了变化,但是entry的hash值不会发生变化,数组位置也不会变,因为不存在listener。当进行get操作时,首先数组下标根据hash值会定位在1上,而2则无法被找到,只能通过数组遍历取得,存在潜在的内存泄露风险。 第二部分我修改一下: 假设hash算法不好,两个对象落在同一个bucket上,由于hashCode改变了,源代码写死了不允许hashCode不同的对象被查找,故同样查找不到,存在潜在的内存泄露风险。 结论:如果一个对象的hashcode改变了,则一定查找不到了。 下面附上测试代码: import java.util.HashMap; import java.util.Map; public class TestMap { public static void main(String[] args) { TestBean bean1 = new TestBean(); bean1.hashCode = 1; Map m = new HashMap(); m.put(bean1, bean1); int j =0,total = 20000;//CAPACITY初始为16,20000个key肯定有落在同一个bucket上的。 for(int i=0;i<total;i++) { bean1.hashCode = i; if(m.get(bean1)==bean1) { j++; } } System.out.println(j); System.out.println((float)j/total); } } class TestBean { static int count = 0; public int hashCode = 0; public int hashCode() { return hashCode; } public String toString() { return "TestBean"+count+"\r\n"; } } PS:我把 equals 去掉了,结果是一样的 |
|
返回顶楼 | |
发表时间:2011-02-16
skzr.org 写道
emsn 写道
youjianbo_han_87 写道
1. 先鄙视下论坛规则,我好久没上了,竟然要回答什么尿尿问题。
2. 贴出 JDK1.5_update_22中 HashMap的 get方法的源码: /** * Returns the value to which the specified key is mapped in this identity * hash map, or <tt>null</tt> if the map contains no mapping for this key. * A return value of <tt>null</tt> does not <i>necessarily</i> indicate * that the map contains no mapping for the key; it is also possible that * the map explicitly maps the key to <tt>null</tt>. The * <tt>containsKey</tt> method may be used to distinguish these two cases. * * @param key the key whose associated value is to be returned. * @return the value to which this map maps the specified key, or * <tt>null</tt> if the map contains no mapping for this key. * @see #put(Object, Object) */ public V get(Object key) { if (key == null) return getForNullKey(); int hash = hash(key.hashCode()); for (Entry<K,V> e = table[indexFor(hash, table.length)]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) return e.value;//--关键在这里。 } return null; } 通过代码可以得知,如果一个Key对应2个Value,看到注释的部分吗? 他按顺序找到后,直接就 Return a.value了。而不会循环Person2. 源代码一贴,神秘感就没了,哈哈
很有道理,从设计角度讲这有不合理的地方,此处仅为讨论问题而故而为之,不推荐像我这么做 |
|
返回顶楼 | |
发表时间:2011-02-16
最后修改:2011-02-16
To superobin:
当equals成立时,hashcode必须相等,don't break the contract 再看看 |
|
返回顶楼 | |
发表时间:2011-02-16
最后修改:2011-02-16
To yangyi:
我要表述的和equals貌似没有关系,代码里我不重载equals结果也是一样的。我要表述的意思是 如果一个放入HashMap的对象的hashCode改变了,则一定查找不到了(在判断的前一半就是false了,根本不会用equals比较了吧)。不管你一共放进map多少个对象、什么顺序、其他对象的key是否与改变后的key一样。 故在这个基础上你写的导致内存泄露的原因是有问题的,虽然结论貌似是一致的。 另:“当equals成立时,hashcode必须相等”这个是固定的约定吗?我怎么记得是“hashcode不同,equals一定返回false”这俩意思好像不太一样,是我记错了? |
|
返回顶楼 | |
发表时间:2011-02-16
superobin 写道 To yangyi:
我要表述的和equals貌似没有关系,代码里我不重载equals结果也是一样的。我要表述的意思是 如果一个放入HashMap的对象的hashCode改变了,则一定查找不到了(在判断的前一半就是false了,根本不会用equals比较了吧)。不管你一共放进map多少个对象、什么顺序、其他对象的key是否与改变后的key一样。 故在这个基础上你写的导致内存泄露的原因是有问题的,虽然结论貌似是一致的。 另:“当equals成立时,hashcode必须相等”这个是固定的约定吗?我怎么记得是“hashcode不同,equals一定返回false”这俩意思好像不太一样,是我记错了? 脱离了general contract再谈就没有意义了,或者我们讨论的不是同一个问题了 |
|
返回顶楼 | |
发表时间:2011-02-16
好文章 持续跟进楼主 期待。
|
|
返回顶楼 | |