精华帖 (0) :: 良好帖 (2) :: 新手帖 (3) :: 隐藏帖 (9)
|
|
---|---|
作者 | 正文 |
发表时间:2011-02-13
最后修改:2011-02-16
市面上的技术书籍琳琅满目,但哥坚信“有代码有真相”,所以,源代码才是最好的学习材料,先不说Java庞大的开源社区提供的充斥着各种设计模式与创新思路的框架代码,就JDK源代码本身就是一部博大精深的技术圣经。去看看jdk源代码中那些署名的@author...无一不是技术大牛,可以学习他们的代码也许是一件让人激动的事情,这也是开源所带给我们的乐趣。好吧,既然又free,又open,干嘛不去看看呢。 首先看java.util中的HashMap与HashTable吧,这对兄弟在各种java面试题中老是被提及,以前只看过面试题答案中的异同点罗列,但是其内部实现及一些特点却未曾深究。个人觉得看源代码不能像看小说那样毫无目的的从头看下来,可以先给自己准备几个问题,做些猜测,然后再去看实现,这样更有针对性。好吧,哥给本次学习准备了几个给自己的问题。 就先从HashMap开始吧。 新建一个Person类: package com.emsn.crazyjdk.java.util; /** * “人”类,重写了equals和hashcode方法...,以id来区分不同的人,你懂的... * * @author emsn1026 * */ public class Person { /** * 身份id */ private String id; /** * 姓名 */ private String name; public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((id == null) ? 0 : id.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Person other = (Person) obj; if (id == null) { if (other.id != null) return false; } else if (!id.equals(other.id)) return false; return true; } @Override public String toString() { return "Person [id=" + id + ", name=" + name + "]"; } } 现在,同样id的人会被认为是同样的实例...当然,不同id的即使姓名相同也是不同的人,那当把这个Person类的实例作为HashMap的key时,key的唯一性将通过people实例的id 来控制。 package com.emsn.crazyjdk.java.util; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import com.emsn.crazyjdk.java.util.Person; /** * @author emsn1026 * */ public class MapTest { /** * @param args */ public static void main(String[] args) { Map m = new HashMap(); Person p1 = new Person(); Person p2 = new Person(); p1.setId("1"); p1.setName("name1"); p2.setId("1"); p2.setName("name2"); m.put(p1, "person1"); m.put(p2, "person2"); System.out.println("Map m's size :" + m.size()); for(Object o :m.entrySet()){ Entry e = (Entry)o; System.out.println("key:"+ e.getKey()); System.out.println("value:"+ e.getValue()); } } } 打印的结果是 Map m's size :1 key:Person [id=1, name=name1] value:person2 可见key已存在,value被覆盖,这个结果可以预测。那么接下来我们把代码修改下: package com.emsn.crazyjdk.java.util; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import com.emsn.crazyjdk.java.util.Person; /** * @author emsn1026 * */ public class MapTest { /** * @param args */ public static void main(String[] args) { Map m = new HashMap(); Person p1 = new Person(); Person p2 = new Person(); p1.setId("1"); p1.setName("name1"); p2.setId("2"); p2.setName("name2"); m.put(p1, "person1"); m.put(p2, "person2"); System.out.println("Map m's size :" + m.size()); p2.setId("1"); System.out.println("Map m's size :" + m.size()); for(Object o :m.entrySet()){ Entry e = (Entry)o; System.out.println("key:"+ e.getKey()); System.out.println("value:"+ e.getValue()); } } } 此处的变化是将p1,p2的id设成不同,然后都作为key插入map,因为两个key不相同,所以我们的预测是都可以插入,此时map的size应该为2,待插入后我们修改p2的id为1,即与p1相同,这样就造成了两个entry的key相同的情况,测试再查看map的结构,看看是不是还是刚才插入的两项。 此时我们不知道HashMap的内部实现,所以我们不知道它的实例会不会在数据插入后还继续维持key的唯一性。 我们可以猜测的是三种答案: 1.抛出异常,不允许修改p2的id与p1相同,维护key的唯一性; 2.可以修改,但根据某种算法删除p1或p2中的一项,也能起到维护key的唯一性; 3.可以修改,没有任何事情发生....两项id相同的person实例并存于map中,即存在同一个key对应了两个value。 那么各位在没尝试并且没有查看过HashMap的源代码时会做出怎样的选择呢? 好,我们跑一下程序。 结果打印如下: Map m's size :2 key:Person [id=1, name=name2] value:person2 key:Person [id=1, name=name1] value:person1 那么是预测的第三种情况...这原本不是我最看好的答案..这样我就有一个疑问了,既然可以有两个相同的key对应不同的value存在,那么我通过这个key应该拿到的value是哪个呢?在上述代码的main方法末尾加入以下两行代码: ... System.out.println("Map m 通过get方法用key p1:"+p1+"时,获取的value:"+m.get(p1)); System.out.println("Map m 通过get方法用key p2:"+p2+"时,获取的value:"+m.get(p2)); ... 得到的结果如下: Map m 通过get方法用key p1:Person [id=1, name=name1]时,获取的value:person1 Map m 通过get方法用key p2:Person [id=1, name=name2]时,获取的value:person1 可见不论你使用p1还是p2,得到的value都是person1。 好吧,现象就先写到这里,在下一篇,我们去边看源代码,边研究这个问题。 下一篇 JDK源代码学习系列一---java.util(2):http://www.iteye.com/topic/907293 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2011-02-13
最后修改:2011-02-14
map是按照散列来找的吧,你把p2修改了之后,就相当于p1的散列了,那样map找的时候不就只能找到p1的了吗,永远都取不到p2的了
|
|
返回顶楼 | |
发表时间:2011-02-14
期待LZ的下篇帖子。
|
|
返回顶楼 | |
发表时间:2011-02-14
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-14
p2的id与p1相同,即修改了p1,p1已经不是之前的p1了又怎么能获取原来的value?记得HashMap是根据hashCode来作为依据存储,读取的
|
|
返回顶楼 | |
发表时间:2011-02-14
隐约记得key 对应的后面貌似一个 列表
|
|
返回顶楼 | |
发表时间:2011-02-14
最后修改:2011-02-14
源码在哪里看呀!!我有jdk文档,但是看到的都是相当于uml图的那种解释!没有源码呀??
|
|
返回顶楼 | |
发表时间:2011-02-14
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-14
楼主这个很经典,重写了hashCode方法,将两个不同的Key保存在HashMap中,然后再修改Key。这样HaspMap中就能出现两个一样的Key了。
然后再取值时,你用到的p1,p2所对应的hash只能是第一个key的hash值为hash(paramObject.hashCode()),根据这个hash值,只能对应一个Entry,所以只能取出第一个值。 public V get(Object paramObject) { if (paramObject == null) return getForNullKey(); int i = hash(paramObject.hashCode()); Entry localEntry = this.table[indexFor(i, this.table.length)]; while (localEntry != null) { Object localObject; if ((localEntry.hash == i) && ((((localObject = localEntry.key) == paramObject) || (paramObject.equals(localObject))))) return localEntry.value; localEntry = localEntry.next; } return null; } |
|
返回顶楼 | |
发表时间:2011-02-14
yangleilt 写道 源码在哪里看呀!!我有jdk文档,但是看到的都是相当于uml图的那种解释!没有源码呀?? 这位同学,下下来的jdk里有源代码的压缩包,名字叫src.zip,可以解压看也可以用eclipse attach进去看,推荐后者。注意,不是jdk文档,你的文档可能是jdk用javadoc 生成的文档。 |
|
返回顶楼 | |