黑马程序员技术交流社区

标题: hashSet更改元素内容, 移除产生的内存泄露问题 [打印本页]

作者: breeze    时间: 2013-5-20 22:57
标题: hashSet更改元素内容, 移除产生的内存泄露问题
本帖最后由 breeze 于 2013-5-24 08:56 编辑

看张老师的视频, 说是更改元素内容后, hashCode值会改变, 与存入集合时的hashCode值不一样, 在集合移除该元素之后, 确实集合中元素个数没有改变, 但是为什么集合中的元素会是y值改变后的元素, 原来的元素呢, set.contains(pt1)的结果是false, 但是为什么集合中会存在修改后的pt1
package com.itcast.reflect;

import java.util.HashSet;

public class HashSetTest {
        public static void main(String[] args) {
                PointTest pt1 = new PointTest(3, 3);
                PointTest pt2 = new PointTest(3, 6);
                PointTest pt3 = new PointTest(3, 3);
                //向set集合中添加了4个元素
                HashSet set = new HashSet();
                set.add(pt1);
                set.add(pt2);
                set.add(pt3);
                set.add(pt1);
               
                //将pt1中的y属性值更改
                pt1.y = 7;
                System.out.println(set.contains(pt1));        //返回的是flase;
                set.remove(pt1);
                //下面会输出3:7, 这个不是不包含的集合中的吗
                System.out.println(set.toArray()[0] + "-------" + set.toArray()[1]);// + "------" + set.toArray()[2]);
                //下面会返回2
                System.out.println(set.size());
        }
}

//对象又两个参数, x,y,重写了hashCode 和equals, toString
class PointTest {
        int x;
        int y;
        
        public PointTest(int x, int y) {
                this.x = x;
                this.y = y;
        }
        @Override
        public int hashCode() {
                final int prime = 31;
                int result = 1;
                result = prime * result + x;
                result = prime * result + y;
                return result;
        }
        @Override
        public boolean equals(Object obj) {
                if (this == obj)
                        return true;
                if (obj == null)
                        return false;
                if (getClass() != obj.getClass())
                        return false;
                PointTest other = (PointTest) obj;
                if (x != other.x)
                        return false;
                if (y != other.y)
                        return false;
                return true;
        }
        
        public String toString() {
                return x + " : " + y;
        }
}
作者: Neverbelazy    时间: 2013-5-21 00:01
本帖最后由 Neverbelazy 于 2013-5-21 00:40 编辑

这是一个非常好的问题, 不得不说,回答这个问题, 让我学到了很多,楼主的思考很犀利!

我查了一些资料, 大概觉得这么解释比较合理:
1. HashSet底层是HashMap实现的, 其中 HashSet<E> 就是 HashMap里面的键值HashMap<E,Object>; 这个Object是什么都可以,因为我们不会用到
源代码:
  1. // 底层使用HashMap来保存HashSet中所有元素
  2. private transient HashMap<E,Object> map;        
  3. // 定义一个虚拟的Object对象作为HashMap的value,将此对象定义为static final
  4. private static final Object PRESENT = new Object();  
复制代码
参考1-HashSet源码说明: http://wenku.baidu.com/view/0199f0ef81c758f5f61f6757.html

2. 而HashMap的底层是以键值对-Entry的形式存在的,而关键就在这里,

3. Entry 是一个接口, Map.Entry是一个内部接口, Entry<K,V> 是内部实现类对象的引用名, 其对象是 Map 进行put(K,V)操作时新生产的, Map的比较性是Entry的hashCode决定的

4. 这个对象中, K,V都是引用的传入K,V的地址以索引对象, 可是,Entry的hashCode 这个 int基本类型, 是以值传递的方式将K的hashCode传进来 int hash=K.hashCode(); 然后 Entry的 hashCode()函数再 return hash; 值——在内部被 记录死了

5. 所以, Entry的比较性,是在你放入Map元素的时候就被定死的了, 之后再怎么在 外边该 pt1的hashCode值,也影响不了Entry里面的和pt1对应的Entry<K,V>的hashCode值

6. 所以, 当你remove的时候,是将 K.hashCode 和 相应的 Entry.hashCode()比较;  因为后期 y=7; 所以调用时, 这两个 hashCode()值 不一致了,就无法 移除—— size =2

7. 所以,K是引用传递, Entry中只记录了一个地址, 所以你用 打印集合中的元素时,还是会通过索引找到相应的 pt1 最新的值 ——也就打印了 3:7
参考2: HashMap源码说明 http://wenku.baidu.com/view/ef2d11d449649b6648d747a9.html

总结:
1. HashSet<K> 实现于 HashMap<K,V>
2. HashMap 的比较性是内部Entry的HashCode决定的
3. Entry的 hashCode()值 是值传递  等于 put(K,V)时 K的 hashCode(); 无法再改变
4. K 是引用 传递,  索取地址可以 得到 最新的成员变量值
作者: xiewen    时间: 2013-5-21 00:07
内存泄漏的两个条件:无用,无法清除。
就是因为p1一直在集合中,但又无法删除,所以会造成内存溢出。
                        黑马云青年为您解答

作者: 袁梦希    时间: 2013-5-21 00:17
xiewen 发表于 2013-5-21 00:07
内存泄漏的两个条件:无用,无法清除。
就是因为p1一直在集合中,但又无法删除,所以会造成内存溢出。
     ...


你看楼上答得多好

尽量再多答点,按照自己的话说,1分鼓励一下,记得认真回答问题才会给分,以后没那么幸运哦
加油:hug:  
作者: xiewen    时间: 2013-5-21 00:20
袁梦希 发表于 2013-5-21 00:17
你看楼上答得多好

尽量再多答点,按照自己的话说,1分鼓励一下,记得认真回答问题才会给分,以后没那么 ...

你也还没睡啊?谢谢你给我加分!
                  黑马云青年为您解答
作者: 袁梦希    时间: 2013-5-21 00:21
xiewen 发表于 2013-5-21 00:20
你也还没睡啊?谢谢你给我加分!
                  黑马云青年为您解答

聊天的时候不用发咱们的后缀   ;P   我没睡呢  睡不着啊
作者: xiewen    时间: 2013-5-21 00:26
袁梦希 发表于 2013-5-21 00:21
聊天的时候不用发咱们的后缀      我没睡呢  睡不着啊

好,谢谢提醒!

作者: 袁梦希    时间: 2013-5-21 00:26
Neverbelazy 发表于 2013-5-21 00:01
这是一个非常好的问题, 不得不说,回答这个问题, 让我学到了很多,楼主的思考很犀利!

我查了一些资料, 大 ...

恩   你答得非常好  加油
作者: Changer_s    时间: 2013-5-22 22:38
  //将pt1中的y属性值更改
                pt1.y = 7;
哈希表中rp1的内存地址改变了,所以原来的rp1也就不复存在了,产生了新的一个对象了!!!
然而我们却不晓得它的对象名叫什么,这样才引起的内存泄露!!!导致无法删除

                        黑马云青年为您解答

作者: 曹睿翔    时间: 2013-5-22 23:13
问题解决就再次编辑更改问题类型,没有解决继续追问
作者: breeze    时间: 2013-5-24 00:06
Neverbelazy 发表于 2013-5-21 00:01
这是一个非常好的问题, 不得不说,回答这个问题, 让我学到了很多,楼主的思考很犀利!

我查了一些资料, 大 ...

回答的非常有条理, 我理解的是原来的pt1对象在hashSet集合给它分配的区域中, 由于对象值改变了, 原来的对象只是没有了引用继续存在于原来的区域, 而移除元素时, 找的是现在对象的hashCode, 相当于新创建了一个对象, 集合中并没有这个hashCode, 但是集合中可以得到新对象的hashCode, 在程序后面接着再移除一次还是没有成功, 那么这时集合中到底还包不包含pt1对象, set.contains(pt1) 返回的false, 就是不包含, 但是set.get(0).hashCode是新对象的, 说明集合中有这个对象, 这个不能理解
作者: Neverbelazy    时间: 2013-5-24 00:43
本帖最后由 Neverbelazy 于 2013-5-24 00:46 编辑
breeze 发表于 2013-5-24 00:06
回答的非常有条理, 我理解的是原来的pt1对象在hashSet集合给它分配的区域中, 由于对象值改变了, 原来的对 ...

1. 注意, HashSet里面隐藏的是 Entry<K,V>的hashCode;remove contains等等比较的都是 K.hashCode() 和对应的 Entry.hashCode();
2. Entry.hashCode()是add(底层调用HashMap的put(K,new Object());)元素时进入集合时 值传入 的K.hashCode();
3. Entry<K,V>里面 K,V都是引用传递
4. 当set.get(0).hashCode()的时候 相当于是分成两步 1) set.get(0) 得到一个对象的引用K; 2) 然后通过这个引用K 索引到这个地址的对象,是引用传递, 所以得到的就是 新的对象的值
作者: breeze    时间: 2013-5-24 00:48
Neverbelazy 发表于 2013-5-24 00:43
1. 注意, HashSet里面隐藏的是 Entry的hashCode;remove contains等等比较的都是 K.hashCode() 和对应的 E ...

那么修改对象时是不是就是创建了一个新的对象, 它和它存入集合中的Entry的hashCode不一样, 所以它相当于不在jihezhong?
作者: Neverbelazy    时间: 2013-5-24 00:57
本帖最后由 Neverbelazy 于 2013-5-24 02:06 编辑
breeze 发表于 2013-5-24 00:48
那么修改对象时是不是就是创建了一个新的对象, 它和它存入集合中的Entry的hashCode不一样, 所以它相当于 ...

1. 具体一点说 新产生的Entry对象无法被删除了,但是 依然可以在 程序中 将 K=null;来杀掉K这个对象; (此处错误,是杀掉这个引用,对象还有引用存在,在Entry<K,V>中已经被传入了K的地址)
2. 所以即使在外部 杀掉 K=null; 这个对象还是没有被杀掉,还存在在内存中
3. 而且Entry这个对象(集合的内部类对象)也存在在集合对象中
4. 所以说, 存在内存泄露
作者: breeze    时间: 2013-5-24 01:01
Neverbelazy 发表于 2013-5-24 00:57
1. 具体一点说 新产生的Entry对象无法被删除了,但是 依然可以在 程序中 将 K=null;来杀掉K这个对象
2. 这 ...

还是不理解 加我QQ聊一下  347670513
作者: 袁梦希    时间: 2013-5-24 01:29
楼主你好,如果问题以解决,请修改分类,谢谢合作。
\




欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) 黑马程序员IT技术论坛 X3.2