`

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>

相关推荐

    基于C/C++开发的单目控制机械臂的上位机程序+视觉识别和关节角反解+源码(高分优秀项目)

    基于C/C++开发的单目控制机械臂的上位机程序+视觉识别和关节角反解+源码,适合毕业设计、课程设计、项目开发。项目源码已经过严格测试,可以放心参考并在此基础上延申使用~ 基于C/C++开发的单目控制机械臂的上位机程序+视觉识别和关节角反解+源码,适合毕业设计、课程设计、项目开发。项目源码已经过严格测试,可以放心参考并在此基础上延申使用~ 基于C/C++开发的单目控制机械臂的上位机程序+视觉识别和关节角反解+源码,适合毕业设计、课程设计、项目开发。项目源码已经过严格测试,可以放心参考并在此基础上延申使用~ 基于C/C++开发的单目控制机械臂的上位机程序+视觉识别和关节角反解+源码,适合毕业设计、课程设计、项目开发。项目源码已经过严格测试,可以放心参考并在此基础上延申使用~

    setuptools-68.2.1-py3-none-any.whl

    Python库是一组预先编写的代码模块,旨在帮助开发者实现特定的编程任务,无需从零开始编写代码。这些库可以包括各种功能,如数学运算、文件操作、数据分析和网络编程等。Python社区提供了大量的第三方库,如NumPy、Pandas和Requests,极大地丰富了Python的应用领域,从数据科学到Web开发。Python库的丰富性是Python成为最受欢迎的编程语言之一的关键原因之一。这些库不仅为初学者提供了快速入门的途径,而且为经验丰富的开发者提供了强大的工具,以高效率、高质量地完成复杂任务。例如,Matplotlib和Seaborn库在数据可视化领域内非常受欢迎,它们提供了广泛的工具和技术,可以创建高度定制化的图表和图形,帮助数据科学家和分析师在数据探索和结果展示中更有效地传达信息。

    springboot 学生信息管理系统.zip

    学生管理系统是一个典型的基于 Spring Boot 的应用程序,旨在帮助学校、教育机构或培训机构管理学生信息、课程安排、成绩等。下面我将介绍一个简单的学生管理系统的设计和实现,基于 Spring Boot 框架。 功能特点 学生信息管理 添加、编辑、删除学生信息。 查询学生信息,支持按姓名、学号等条件查询。

    setuptools-0.9.8-py2.py3-none-any.whl

    Python库是一组预先编写的代码模块,旨在帮助开发者实现特定的编程任务,无需从零开始编写代码。这些库可以包括各种功能,如数学运算、文件操作、数据分析和网络编程等。Python社区提供了大量的第三方库,如NumPy、Pandas和Requests,极大地丰富了Python的应用领域,从数据科学到Web开发。Python库的丰富性是Python成为最受欢迎的编程语言之一的关键原因之一。这些库不仅为初学者提供了快速入门的途径,而且为经验丰富的开发者提供了强大的工具,以高效率、高质量地完成复杂任务。例如,Matplotlib和Seaborn库在数据可视化领域内非常受欢迎,它们提供了广泛的工具和技术,可以创建高度定制化的图表和图形,帮助数据科学家和分析师在数据探索和结果展示中更有效地传达信息。

    利用python的pyautogui函数实现简单的自动化操作

    1.安装python3.4以上版本,并配置环境变量(目前有装3.9遇到坑的,我个人用的3.7.6) 教程:https://www.runoob.com/python3/python3-install.html 2.安装依赖包 方法:在cmd中(win+R 输入cmd 回车)输入 pip install pyperclip 回车 pip install xlrd 回车 pip install pyautogui==0.9.50 回车 pip install opencv-python -i https://pypi.tuna.tsinghua.edu.cn/simple 回车 pip install pillow 回车 这几步如果哪步没成功,请自行百度 如 pip install opencv-python失败 3.把每一步要操作的图标、区域截图保存至本文件夹 png格式(注意如果同屏有多个相同图标,回默认找到最左上的一个,因此怎么截图,截多大的区域,是个学问,如输入框只截中间空白部分肯定是不行的,宗旨就是“唯一”) 4.在cmd.xls 的sheet1 中,配置每一步的指令,如指

    setuptools-38.2.0.zip

    Python库是一组预先编写的代码模块,旨在帮助开发者实现特定的编程任务,无需从零开始编写代码。这些库可以包括各种功能,如数学运算、文件操作、数据分析和网络编程等。Python社区提供了大量的第三方库,如NumPy、Pandas和Requests,极大地丰富了Python的应用领域,从数据科学到Web开发。Python库的丰富性是Python成为最受欢迎的编程语言之一的关键原因之一。这些库不仅为初学者提供了快速入门的途径,而且为经验丰富的开发者提供了强大的工具,以高效率、高质量地完成复杂任务。例如,Matplotlib和Seaborn库在数据可视化领域内非常受欢迎,它们提供了广泛的工具和技术,可以创建高度定制化的图表和图形,帮助数据科学家和分析师在数据探索和结果展示中更有效地传达信息。

    java毕业设计之鲜花销售网站的设计与实现源码.zip

    毕业设计之鲜花销售网站的设计与实现源码.zip毕业设计之鲜花销售网站的设计与实现源码.zip毕业设计之鲜花销售网站的设计与实现源码.zip毕业设计之鲜花销售网站的设计与实现源码.zip

    药店销售管理系统ssm(药品销售)【说明】资源来源网络以及部分开源社区、仅供参考与学习、项目不可商用、一切后果由使用者承担、若

    药店销售管理系统ssm(药品销售)【说明】资源来源网络以及部分开源社区、仅供参考与学习、项目不可商用、一切后果由使用者承担、若是侵权请联系删除

    JDK1.8 API 中文文档 高清完整版 CHM.zip

    JDK1.8 API 中文文档 高清完整版 CHM

    母亲节快乐python脚本

    母亲节快乐python脚本

    华中科技大学电信专业 课程资料 作业 代码 实验报告-通信电子线路-内含源码和说明书.zip

    华中科技大学电信专业 课程资料 作业 代码 实验报告-通信电子线路-内含源码和说明书.zip

    1999-2022年各省城镇居民人均消费支出数据(无缺失).xls

    1999-2022年各省城镇居民人均消费支出数据(无缺失) 1、时间:1999-2022年 2、来源:国家统计J、统计NJ 3、指标:城镇居民人均消费支出 4、范围:31省 5、缺失情况:无缺失

    setuptools-49.2.1-py3-none-any.whl

    Python库是一组预先编写的代码模块,旨在帮助开发者实现特定的编程任务,无需从零开始编写代码。这些库可以包括各种功能,如数学运算、文件操作、数据分析和网络编程等。Python社区提供了大量的第三方库,如NumPy、Pandas和Requests,极大地丰富了Python的应用领域,从数据科学到Web开发。Python库的丰富性是Python成为最受欢迎的编程语言之一的关键原因之一。这些库不仅为初学者提供了快速入门的途径,而且为经验丰富的开发者提供了强大的工具,以高效率、高质量地完成复杂任务。例如,Matplotlib和Seaborn库在数据可视化领域内非常受欢迎,它们提供了广泛的工具和技术,可以创建高度定制化的图表和图形,帮助数据科学家和分析师在数据探索和结果展示中更有效地传达信息。

    基于单片机的电梯程序控制系统.zip

    基于单片机的系统

    setuptools-20.6.8.zip

    Python库是一组预先编写的代码模块,旨在帮助开发者实现特定的编程任务,无需从零开始编写代码。这些库可以包括各种功能,如数学运算、文件操作、数据分析和网络编程等。Python社区提供了大量的第三方库,如NumPy、Pandas和Requests,极大地丰富了Python的应用领域,从数据科学到Web开发。Python库的丰富性是Python成为最受欢迎的编程语言之一的关键原因之一。这些库不仅为初学者提供了快速入门的途径,而且为经验丰富的开发者提供了强大的工具,以高效率、高质量地完成复杂任务。例如,Matplotlib和Seaborn库在数据可视化领域内非常受欢迎,它们提供了广泛的工具和技术,可以创建高度定制化的图表和图形,帮助数据科学家和分析师在数据探索和结果展示中更有效地传达信息。

    分布式系统课程实验-内含源码和说明书.zip

    分布式系统课程实验-内含源码和说明书.zip

    setuptools-69.0.1-py3-none-any.whl

    Python库是一组预先编写的代码模块,旨在帮助开发者实现特定的编程任务,无需从零开始编写代码。这些库可以包括各种功能,如数学运算、文件操作、数据分析和网络编程等。Python社区提供了大量的第三方库,如NumPy、Pandas和Requests,极大地丰富了Python的应用领域,从数据科学到Web开发。Python库的丰富性是Python成为最受欢迎的编程语言之一的关键原因之一。这些库不仅为初学者提供了快速入门的途径,而且为经验丰富的开发者提供了强大的工具,以高效率、高质量地完成复杂任务。例如,Matplotlib和Seaborn库在数据可视化领域内非常受欢迎,它们提供了广泛的工具和技术,可以创建高度定制化的图表和图形,帮助数据科学家和分析师在数据探索和结果展示中更有效地传达信息。

    setuptools-49.3.1-py3-none-any.whl

    Python库是一组预先编写的代码模块,旨在帮助开发者实现特定的编程任务,无需从零开始编写代码。这些库可以包括各种功能,如数学运算、文件操作、数据分析和网络编程等。Python社区提供了大量的第三方库,如NumPy、Pandas和Requests,极大地丰富了Python的应用领域,从数据科学到Web开发。Python库的丰富性是Python成为最受欢迎的编程语言之一的关键原因之一。这些库不仅为初学者提供了快速入门的途径,而且为经验丰富的开发者提供了强大的工具,以高效率、高质量地完成复杂任务。例如,Matplotlib和Seaborn库在数据可视化领域内非常受欢迎,它们提供了广泛的工具和技术,可以创建高度定制化的图表和图形,帮助数据科学家和分析师在数据探索和结果展示中更有效地传达信息。

    基于Scala+Flink实现实时冰蝎(Behinder)流量检测源码+部署文档+全部资料齐全 高分项目.zip

    【资源说明】 基于Scala+Flink实现实时冰蝎(Behinder)流量检测源码+部署文档+全部资料齐全 高分项目.zip基于Scala+Flink实现实时冰蝎(Behinder)流量检测源码+部署文档+全部资料齐全 高分项目.zip 【备注】 1、该项目是个人高分项目源码,已获导师指导认可通过,答辩评审分达到95分 2、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用! 3、本项目适合计算机相关专业(人工智能、通信工程、自动化、电子信息、物联网等)的在校学生、老师或者企业员工下载使用,也可作为毕业设计、课程设计、作业、项目初期立项演示等,当然也适合小白学习进阶。 4、如果基础还行,可以在此代码基础上进行修改,以实现其他功能,也可直接用于毕设、课设、作业等。 欢迎下载,沟通交流,互相学习,共同进步!

    setuptools-25.2.0.zip

    Python库是一组预先编写的代码模块,旨在帮助开发者实现特定的编程任务,无需从零开始编写代码。这些库可以包括各种功能,如数学运算、文件操作、数据分析和网络编程等。Python社区提供了大量的第三方库,如NumPy、Pandas和Requests,极大地丰富了Python的应用领域,从数据科学到Web开发。Python库的丰富性是Python成为最受欢迎的编程语言之一的关键原因之一。这些库不仅为初学者提供了快速入门的途径,而且为经验丰富的开发者提供了强大的工具,以高效率、高质量地完成复杂任务。例如,Matplotlib和Seaborn库在数据可视化领域内非常受欢迎,它们提供了广泛的工具和技术,可以创建高度定制化的图表和图形,帮助数据科学家和分析师在数据探索和结果展示中更有效地传达信息。

Global site tag (gtag.js) - Google Analytics