`

JDK源代码学习系列一---java.util(1)

    博客分类:
  • Java
阅读更多
    好吧,我承认我比较懒~ 但是发现不把一些学习成果与工作经验记录下来,我会慢慢将它们遗忘掉,最后一无所有。新年回来,2011从今天开始重新积累吧。
    市面上的技术书籍琳琅满目,但哥坚信“有代码有真相”,所以,源代码才是最好的学习材料,先不说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
分享到:
评论
37 楼 独爱Java 2011-04-14  
虽然看不懂,但是强烈希望能进一步学习。留个名。。。
36 楼 emsn 2011-02-17  
superobin 写道
To:yangyi
可能之前说的有点冲,但是没有恶意,你也是个对技术问题很执著的人,我对之前的鲁莽表示歉意~
经过你的举例我发现我之前的结论确实有问题,在极端的情况下,它确实可以取出来。后来经过实例测试也验证了这一点。并不是永远都取不出来。

另外,我认為对於各种问题,尤其是像这种有明确代码的一是一二是二的问题,应该没啥周旋的餘地,这种问题如果有分歧,两个个人肯定有一个错的,如果找不到错在哪,至少有一个人要坚持著错误的观点,这样不好,不对吗?
今天坚持了错误的观点很长时间,经过讨论学到了很多——越是这样的讨论学到的越多,思考越深入。比如今天我就知道了hashCode和equals之间的约定,之前只有个模糊的概念现在也终於清晰了。

再另外,佔领楼主的帖子争论了这麼多,再对楼主表现一下歉意吧~呵呵


原本的目的就是让大家讨论的,技术问题方面我们对事不对人,该争论的还是得争论,这是一种学习态度。
35 楼 zhaolei415 2011-02-17  
java中典型的内存泄露问题啊,修改的字段参与了对象的hashcode的计算,造成存储位置变更
34 楼 superobin 2011-02-17  
To:yangyi
可能之前说的有点冲,但是没有恶意,你也是个对技术问题很执著的人,我对之前的鲁莽表示歉意~
经过你的举例我发现我之前的结论确实有问题,在极端的情况下,它确实可以取出来。后来经过实例测试也验证了这一点。并不是永远都取不出来。

另外,我认為对於各种问题,尤其是像这种有明确代码的一是一二是二的问题,应该没啥周旋的餘地,这种问题如果有分歧,两个个人肯定有一个错的,如果找不到错在哪,至少有一个人要坚持著错误的观点,这样不好,不对吗?
今天坚持了错误的观点很长时间,经过讨论学到了很多——越是这样的讨论学到的越多,思考越深入。比如今天我就知道了hashCode和equals之间的约定,之前只有个模糊的概念现在也终於清晰了。

再另外,佔领楼主的帖子争论了这麼多,再对楼主表现一下歉意吧~呵呵
33 楼 emsn 2011-02-16  
我觉得yangyi说的情况是存在的。我现在有个疑问,就是HashMap的hash方法计算不同的hashcode返回的hash值有可能相同吗?我想知道hash冲突发生在仅仅是不同的key对应相同的hashcode这种情况,还是不同的hashcode也会生成相同的hash值?
32 楼 yangyi 2011-02-16  
superobin 写道
不算跑题吧,楼主谈的这种情况只是我说的这种情况的一种特例,可以从我说这种情况推断出来。我只是不想让 看似有道理但是不算完美 的解释方法(我认为)摆在最后而已。

既然放进去任何对象只要hashCode改变就拿不出来(不用提equals,得出这个结论和equals一点关系都没),那么楼主的结论和你所解释的原因就都问题(如果我的解释是正确的)。
如果你觉得这个是跑题,一直认为自己解释的是正确的话,烦请指出我的错误在哪,否则我也认为就真没什么必要继续讨论了。

首先您消消火,我回复也是看您很认真对待这个问题,如果不回复可能您还在生闷气。下面表达一下我的意见,您要是不同意也没关系,我们保留意见就好了,何必非要意见一致呢,没准某个特定场合就发生了您说的情况,那您的分析比我的有用多了。
首先逻辑上讲,如果公理都不承认的话讨论定理还有意义吗?太极生两仪,四象演八卦。道之不存,器又焉附呢?没有1+1=2这个“共识”,又怎么敢大胆的说2+1=3呢
其次,假如我们承认了公理,
引用

Whenever it is invoked on the same object more than once during an execution of a Java application, the hashCode method must consistently return the same integer, provided no information used in 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.
If two objects are equal according to the equals(Object) method, then calling the hashCode method on each of the two objects must produce the same integer result.
It is not required that if two objects are unequal according to the equals(java.lang.Object) method, then calling the 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 hashtables.

http://download.oracle.com/javase/1.5.0/docs/api/java/lang/Object.html
根据公理,对于我们的场景,可以分析出两点:
1 hashcode是可以变化的,但是一定要equals所依赖的信息变化才行
2 当equals成立时,必有hashcode相等
回头说我们的场景,
当两个对象Key不相等,并且当两次put发生在同一个bucket中时,第一次table[x] = P1,第二次table[x] = P2, P2->next = P1
当P2的key发生变化,并变为和P1一致时:
a)此变化满足条件1,因此可行
b)根据条件2,此时P2的新的hash值等于之前P1的hash值,同时也是P2之前的hash值
c)当根据P1或P2作为key取值时,首先hash值都相等,其次equals成立,第三首先定位到P2,所以返回P2
31 楼 skzr.org 2011-02-16  
<div class="quote_title">superobin 写道</div>
<div class="quote_div">不算跑题吧,楼主谈的这种情况只是我说的这种情况的一种特例,可以从我说这种情况推断出来。<span style="color: green;">我只是不想让 看似有道理但是不算完美 的解释方法(我认为)摆在最后而已。</span><br><br>既然放进去任何对象只要hashCode改变就拿不出来(不用提equals,得出这个结论和equals一点关系都没),那么楼主的结论和你所解释的原因就都问题(如果我的解释是正确的)。<br>如果你觉得这个是跑题,一直认为自己解释的是正确的话,烦请指出我的错误在哪,否则我也认为就真没什么必要继续讨论了。<br>
</div>
<p><br><br>对于所有对象都通用的方法<br><br>Object中的方法:equals,hashCode,toString,clone 和 finalize都有明确的通用约定,所以我们需要遵守,因为不少的类都是按照通用约定来工作的<br><br>这里说说的不少的类就包括java.util.*中的Map,List等等</p>
<p> </p>
<p>特别注意一个就是comparate接口,既然equals都判断true那么compare结果也应当为0</p>
<p> </p>
<p>这些通用约定如果未注意很容易让里的程序或设计出现非预期结果的</p>
30 楼 superobin 2011-02-16  
不算跑题吧,楼主谈的这种情况只是我说的这种情况的一种特例,可以从我说这种情况推断出来。我只是不想让 看似有道理但是不算完美 的解释方法(我认为)摆在最后而已。

既然放进去任何对象只要hashCode改变就拿不出来(不用提equals,得出这个结论和equals一点关系都没),那么楼主的结论和你所解释的原因就都问题(如果我的解释是正确的)。
如果你觉得这个是跑题,一直认为自己解释的是正确的话,烦请指出我的错误在哪,否则我也认为就真没什么必要继续讨论了。
29 楼 xici_magic 2011-02-16  
好文章 持续跟进楼主 期待。
28 楼 yangyi 2011-02-16  
superobin 写道
To yangyi:
我要表述的和equals貌似没有关系,代码里我不重载equals结果也是一样的。我要表述的意思是

如果一个放入HashMap的对象的hashCode改变了,则一定查找不到了(在判断的前一半就是false了,根本不会用equals比较了吧)。不管你一共放进map多少个对象、什么顺序、其他对象的key是否与改变后的key一样。

故在这个基础上你写的导致内存泄露的原因是有问题的,虽然结论貌似是一致的。

另:“当equals成立时,hashcode必须相等”这个是固定的约定吗?我怎么记得是“hashcode不同,equals一定返回false”这俩意思好像不太一样,是我记错了?

脱离了general contract再谈就没有意义了,或者我们讨论的不是同一个问题了
27 楼 superobin 2011-02-16  
To yangyi:
我要表述的和equals貌似没有关系,代码里我不重载equals结果也是一样的。我要表述的意思是

如果一个放入HashMap的对象的hashCode改变了,则一定查找不到了(在判断的前一半就是false了,根本不会用equals比较了吧)。不管你一共放进map多少个对象、什么顺序、其他对象的key是否与改变后的key一样。

故在这个基础上你写的导致内存泄露的原因是有问题的,虽然结论貌似是一致的。

另:“当equals成立时,hashcode必须相等”这个是固定的约定吗?我怎么记得是“hashcode不同,equals一定返回false”这俩意思好像不太一样,是我记错了?
26 楼 yangyi 2011-02-16  
To superobin:
当equals成立时,hashcode必须相等,don't break the contract
再看看
25 楼 emsn 2011-02-16  
<div class="quote_title">skzr.org 写道</div>
<div class="quote_div">
<div class="quote_title">emsn 写道</div>
<div class="quote_div">
<div class="quote_title">youjianbo_han_87 写道</div>
<div class="quote_div">1. 先鄙视下论坛规则,我好久没上了,竟然要回答什么尿尿问题。<br>2. 贴出 JDK1.5_update_22中 HashMap的 get方法的源码:<br>/**<br>     * Returns the value to which the specified key is mapped in this identity<br>     * hash map, or &lt;tt&gt;null&lt;/tt&gt; if the map contains no mapping for this key.<br>     * A return value of &lt;tt&gt;null&lt;/tt&gt; does not &lt;i&gt;necessarily&lt;/i&gt; indicate<br>     * that the map contains no mapping for the key; it is also possible that<br>     * the map explicitly maps the key to &lt;tt&gt;null&lt;/tt&gt;. The<br>     * &lt;tt&gt;containsKey&lt;/tt&gt; method may be used to distinguish these two cases.<br>     *<br>     * @param   key the key whose associated value is to be returned.<br>     * @return  the value to which this map maps the specified key, or<br>     *          &lt;tt&gt;null&lt;/tt&gt; if the map contains no mapping for this key.<br>     * @see #put(Object, Object)<br>     */<br>    public V get(Object key) {<br> if (key == null)<br>     return getForNullKey();<br>        int hash = hash(key.hashCode());<br>        for (Entry&lt;K,V&gt; e = table[indexFor(hash, table.length)];<br>             e != null;<br>             e = e.next) {<br>            Object k;<br>            if (e.hash == hash &amp;&amp; ((k = e.key) == key || key.equals(k)))<br>                return e.value;//--关键在这里。<br>        }<br>        return null;<br>    }<br><br>通过代码可以得知,如果一个Key对应2个Value,看到注释的部分吗? 他按顺序找到后,直接就 Return a.value了。而不会循环Person2.</div>
<br><img src="/images/smiles/icon_arrow.gif" alt=""> 源代码一贴,神秘感就没了,哈哈</div>
<p><br><br>源码就是硬道理,不过[youjianbo_han_87]可能理解错了楼主的意图了,楼主是在put后,修改了key的关键域字段,人为改变了对象的hashCode和equals的行为(相对put时的状态),给我们设计提了个醒哦:key尽量使用状态不可变的类(偶一般都是Long、Integer、String做key)</p>
<p> </p>
<p><br>这是个坏的例子:既然person使用id做为关键域,逻辑上关键域就不应当再修改了,否则程序或代码行为不可预知。</p>
<p><br>呵呵,以前项目中用map做缓存,都是用的String做key,就是看中了String不可变,建议缓存key对象中这样的关键域做成了final,呵呵调用的想是坏都不行(也遇到过和楼主一样的情形,调用者重用了返回的key,搞三搞四,调试了蛮久才发现,后来基本上返回的尽量new或者clone一个对象)<br><br><strong><span style="color: #ff0000; font-size: medium;">map只认put时的key状态,put后对key修改了关键域-&gt;改变了hashCode和equals行为,当然再次get时会按照新的hashCode和equals来定位Entry了</span></strong></p>
<p> </p>
<p> </p>
<p> </p>
</div>
<p>很有道理,从设计角度讲这有不合理的地方,此处仅为讨论问题而故而为之,不推荐像我这么做</p>
24 楼 superobin 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 去掉了,结果是一样的
23 楼 yangyi 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则是潜在的内存泄露
22 楼 shhbobby 2011-02-15  
很好,我也要一起过一遍
21 楼 elliotann 2011-02-15  
哎,JDK编码格式可读性还真差
20 楼 superobin 2011-02-15  
改hashCode以后,在map中就找不到这个对象了。具体可参见源码,刚才理解错了,,源码中有个hash == key.hash这种情况的判断,旧的hash是会被存储的,改了hashCode新hash和旧hash应该不会一致吧。。。

19 楼 yangleilt 2011-02-15  
emsn 写道
yangleilt 写道
源码在哪里看呀!!我有jdk文档,但是看到的都是相当于uml图的那种解释!没有源码呀??

这位同学,下下来的jdk里有源代码的压缩包,名字叫src.zip,可以解压看也可以用eclipse attach进去看,推荐后者。注意,不是jdk文档,你的文档可能是jdk用javadoc 生成的文档。

以前还没发现呀!谢谢了!
18 楼 youjianbo_han_87 2011-02-15  
<div class="quote_title">skzr.org 写道</div>
<div class="quote_div">
<div class="quote_title">emsn 写道</div>
<div class="quote_div">
<div class="quote_title">youjianbo_han_87 写道</div>
<div class="quote_div">1. 先鄙视下论坛规则,我好久没上了,竟然要回答什么尿尿问题。<br>2. 贴出 JDK1.5_update_22中 HashMap的 get方法的源码:<br>/**<br>     * Returns the value to which the specified key is mapped in this identity<br>     * hash map, or &lt;tt&gt;null&lt;/tt&gt; if the map contains no mapping for this key.<br>     * A return value of &lt;tt&gt;null&lt;/tt&gt; does not &lt;i&gt;necessarily&lt;/i&gt; indicate<br>     * that the map contains no mapping for the key; it is also possible that<br>     * the map explicitly maps the key to &lt;tt&gt;null&lt;/tt&gt;. The<br>     * &lt;tt&gt;containsKey&lt;/tt&gt; method may be used to distinguish these two cases.<br>     *<br>     * @param   key the key whose associated value is to be returned.<br>     * @return  the value to which this map maps the specified key, or<br>     *          &lt;tt&gt;null&lt;/tt&gt; if the map contains no mapping for this key.<br>     * @see #put(Object, Object)<br>     */<br>    public V get(Object key) {<br> if (key == null)<br>     return getForNullKey();<br>        int hash = hash(key.hashCode());<br>        for (Entry&lt;K,V&gt; e = table[indexFor(hash, table.length)];<br>             e != null;<br>             e = e.next) {<br>            Object k;<br>            if (e.hash == hash &amp;&amp; ((k = e.key) == key || key.equals(k)))<br>                return e.value;//--关键在这里。<br>        }<br>        return null;<br>    }<br><br>通过代码可以得知,如果一个Key对应2个Value,看到注释的部分吗? 他按顺序找到后,直接就 Return a.value了。而不会循环Person2.</div>
<br><img src="/images/smiles/icon_arrow.gif" alt=""> 源代码一贴,神秘感就没了,哈哈</div>
<p><br><br>源码就是硬道理,不过[youjianbo_han_87]可能理解错了楼主的意图了,楼主是在put后,修改了key的关键域字段,人为改变了对象的hashCode和equals的行为(相对put时的状态),给我们设计提了个醒哦:key尽量使用状态不可变的类(偶一般都是Long、Integer、String做key)</p>
<p> </p>
<p><br>这是个坏的例子:既然person使用id做为关键域,逻辑上关键域就不应当再修改了,否则程序或代码行为不可预知。</p>
<p><br>呵呵,以前项目中用map做缓存,都是用的String做key,就是看中了String不可变,建议缓存key对象中这样的关键域做成了final,呵呵调用的想是坏都不行(也遇到过和楼主一样的情形,调用者重用了返回的key,搞三搞四,调试了蛮久才发现,后来基本上返回的尽量new或者clone一个对象)<br><br><strong><span style="color: #ff0000; font-size: medium;">map只认put时的key状态,put后对key修改了关键域-&gt;改变了hashCode和equals行为,当然再次get时会按照新的hashCode和equals来定位Entry了</span></strong></p>
<p> </p>
</div>
<p>我不知道哥们你看懂了没,我根本就忽略了他Put的过程,我只是用源码说明,如果Map中一个Key对应了2个Value,如例子中的Person1和Person2为何只能取到前者,至于Put,你看看源码不就知道了。。。。。</p>

相关推荐

    jetty-util-8.1.8.v20121106-API文档-中文版.zip

    赠送源代码:jetty-util-8.1.8.v20121106-sources.jar; 赠送Maven依赖信息文件:jetty-util-8.1.8.v20121106.pom; 包含翻译后的API文档:jetty-util-8.1.8.v20121106-javadoc-API文档-中文(简体)版.zip; Maven...

    backport-util-concurrent-3.1.jar

    backport-util-concurrent-3.1.jar是一个Java库,它提供了一些并发工具类,用于简化多线程编程。这个库包含了许多实用的工具类,如`FutureTask`、`CountDownLatch`、`Semaphore`等,这些工具类可以帮助开发者更好地...

    javajdk源码-java.util_source_learning:学习JDK源代码

    java jdk源码jdk_source_learning 学习JDK源代码

    java-swing管理系统毕业设计源码-学生信息管理(文档+视频+源码)-计算机毕业设计源代码.rar

    这款Java swing实现的学生信息管理系统和jsp版本的功能很相似,简单的实现了班级信息的增删改查,学生信息的增删改查,数据库采用的是mysql,jdk版本不限,是Java学习者学习参考非常好的一个小项目,下面我们来看看...

    javajdk1.8源码-Java-source-reading:jdk1.8源代码分析

    近期计划:以jdk为主,java.lang和java.util下一些重要的类以及juc,将来可能会写web框架相关 jdk1.8 java.lang Integer String java.util Arrays ArrayList LinkedList HashMap HashSet LinkedHashMap

    [Java参考文档].JDK_API 1.6

    java.sql 提供使用 JavaTM 编程语言访问并处理存储在数据源(通常是一个关系数据库)中的数据的 API。 java.text 提供以与自然语言无关的方式来处理文本、日期、数字和消息的类和接口。 java.text.spi java.text ...

    java jdk实列宝典 光盘源代码

    java为数据结构中的列表定义了一个接口类java.util.list同时提供了3个实现类,分别是ArrayList、Vector、LinkedList使用; 生成不重复的随机数序列;列表、集合与数组的互相转换;java为数据结构中的映射定义一个接口...

    数据库增删改查的源代码

    非常实用的数据库增删改查的源代码; DB.java package com.util; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import ...

    SM4加解密代码参考.java

    源代码 需要的jar :SM4Util.jar 和bcprov-jdk15on-1.60.jar SM4Util.jar 可以本人上传的资源下载

    spring-hibernate-dwr实例

    spring-hibernate-dwr做的AJAX操作CRUD实例 环境:myeclipse6.0+jdk1.6 所需lib列表,请自行加入 mysql-connector-java-3.1.7-bin.jar antlr-2.7.6rc1.jar asm-attrs.jar cglib-2.1.3.jar ...

    java api最新7.0

    java.sql 提供使用 JavaTM 编程语言访问并处理存储在数据源(通常是一个关系数据库)中的数据的 API。 java.text 提供以与自然语言无关的方式来处理文本、日期、数字和消息的类和接口。 java.text.spi java.text ...

    Java学习笔记7.0

    《Java JDK6学习笔记》是作者良葛格本人近几年来学习Java的心得笔记,结构按照作者的学习脉络依次展开,从什么是Java、如何配置Java开发环境、基本的Java语法到程序流程控制、管理类文件、异常处理、枚举类型、泛型...

    Java 1.6 API 中文 New

    java.sql 提供使用 JavaTM 编程语言访问并处理存储在数据源(通常是一个关系数据库)中的数据的 API。 java.text 提供以与自然语言无关的方式来处理文本、日期、数字和消息的类和接口。 java.text.spi java.text ...

    JDK_1_6 API

    java.sql 提供使用 JavaTM 编程语言访问并处理存储在数据源(通常是一个关系数据库)中的数据的 API。 java.text 提供以与自然语言无关的方式来处理文本、日期、数字和消息的类和接口。 java.text.spi java.text ...

    java JDK5.0 实例开发宝典

    Jdk5.0 源代码使用说明 &lt;br&gt;1. 类型基本操作 2. 面向对象的操作 适配器模式 单列模式 工程模式 组合模式。。。 3. 精确计算数字和随机数字 4. java.util.package高级使用 List Set collection.. 5. ...

    javaapi和源码-javase_review:回顾JavaSE深入了解源代码,并实现JDKAPI。源代码分析文章可以找到@我的博客

    java api和源码 javase_review review java SE deeping with source code,and implements the JDK API. the source code analyse articles can find @my blog 参考资料: 视频: 【1】. 刘意JavaSE视频--【主导】 ...

    [Java参考文档]

    jdk_api_1_6帮助开发 java.applet 提供创建 applet 所必需的类和 applet 用来与其 applet 上下文通信的类。 java.awt 包含用于创建用户界面和绘制图形图像的所有类。 java.awt.color 提供用于颜色空间的类。 ...

    JDK1.8:阅读和注释JDK源代码-jdk1.8 source code

    JDK1.8 介绍 JDK原始阅读,注释 文件开头的备注,是关于此类的重点或关键问题 ///开头或/\*/开头的注释,是阅读过程中添加的 内容 java.util.concurrent 质量管理体系 收藏 java.util.stream java.util.function ...

    贪吃蛇java课程设计--贪吃蛇程序设计.doc

    " 沈 阳 大 学 课程设计说明书 NO.4 "2.4设计的源代码 " "import java.awt.*; " "import java.awt.event.*; " "import javax.swing.*; " "import java.util.*; " "public class GreedSnake implements KeyListener ...

    java打包源码-ga-ref-impl:www.geometricalgebra.net打包了Java源代码以使用常见的Maven/Java

    只要您已经通过Java7(JDK,而不仅仅是JRE!您需要编译器!),Maven就会简化编译和构建软件的过程。 Maven首先将这个几何代数代码“ colt”线性代数库作为依赖项,下载Internet上的每个jar文件。 目标 mvn test#...

Global site tag (gtag.js) - Google Analytics