C# 中map的containKey的问题

问题:当一个map在遍历时想找到其中的Key时,使用containKey(key)时,却发现返回值为false:

这个会困扰一个人很长时间,所以为节省小伙伴们的时间,来看看原因吧,帮助纠结的你回答上面的问题

我们忽略了某些东西,倘若我们想用Map存储自定义类会发生什么?

1)自定义类作为Map的Value:

复制代码
 1 import java.util.HashMap;
 2 import java.util.Map;
 3 
 4 class MyClass {
 5     private static int counter = 0;
 6     private int id;
 7 
 8     public MyClass() {
 9         id = ++counter;
10     }
11 
12     @Override
13     public String toString() {
14         return "自定义类" + id;
15     }
16 }
17 
18 public class TestMap {
19     public static void main(String[] args) {
20         Map<String, MyClass> testValue = new HashMap<String, MyClass>();
21         MyClass mc1 = new MyClass();
22         MyClass mc2 = new MyClass();
23         testValue.put("NO.1", mc1);
24         testValue.put("NO.2", mc2);
25         System.out.println(testValue);
26     }
27 }/*
28 {NO.1=自定义类1, NO.2=自定义类2}
29 *//:~
复制代码

我声明了一个MyClass,它给每一个对象会自动分配一个id,并且重载了toString()。在Map中key我用了String,value我用了我们自定义的类,大家会发现毫无问题,轻轻松松就打印出了我们期望得到的结果。难道事情就是这样简单?那么我们再来将自定义类作为key来试试

2)自定义类作为Map的Key(MyClass不变):

复制代码
 1 public class TestMap {
 2     public static void main(String[] args) {
 3         Map<MyClass, String> testKey = new HashMap<MyClass, String>();
 4         MyClass mc1 = new MyClass();
 5         MyClass mc2 = new MyClass();
 6         testKey.put(mc1, "NO.1");
 7         testKey.put(mc2, "NO.2");
 8         System.out.println(testKey);
 9 
10         // equal()
11         mc1 = new MyClass();
12         mc1.id = 1;
13         testKey.put(mc1, "NO.3");
14         System.out.println(testKey);
15         System.out.println("***********");
16         
17         // hashcode()
18         mc1 = new MyClass();
19         mc1.id = 1;
20         System.out.println(mc1);
21         System.out.println(testKey);
22         System.out.println(testKey.containsKey(mc1));
23     }
24 }/*
25 {自定义类1=NO.1, 自定义类2=NO.2}
26 {自定义类1=NO.1, 自定义类2=NO.2, 自定义类1=NO.3}
27 ***********
28 自定义类1
29 {自定义类1=NO.1, 自定义类2=NO.2, 自定义类1=NO.3}
30 false
31 *///:~
复制代码

不知看完代码以及其输出结果后,是否发现了一些问题。

1.我们在前面讨论过Map在key上具有Set的性质,即Map中Key值是不能重复但是在代码26行我们会惊奇的发现运行结果竟然出现了两个相同的key但是有不同的value;

2.在代码18行我将mc1重新new了下,但是我将它的id又设为了1,mc1还是自定义类1,那么testKey中应该有的(而且还有两个!-_-),但是containKey返回的却是false。

要解答这些问题就要引出一个很重要的概念散列码:

1)什么是散列码?

不必用冗长的线性搜索技术来查找一个键,而是用一个特殊的值,名为“散列码”。散列码可以获取对象中的信息,然后将其转换成那个对象“相对唯一”的整数(int)。这是我从百度百科copy来的一段定义。

相信大家对散列表(或说哈希表)都有了解,我也将维基百科的链接放了上来大家可以去看下。其中有一个散列函数(即哈希函数),而散列函数产生的值就是散列码。

2)为什么需要散列码

大家都知道散列表,散列表能够是我们以O(1)的代价进行查询,那么散列函数的作用就是使每一个对象产生一个散列码,这个散列码将用来映射到内存地址空间,这样在进行查询时就能使得复杂度达到O(1),这其实就是哈希表的知识。

hashCode()和equals()

观察Java的源代码我们很容易发现Object基类中有两个函数hashCode()和equals()。hashCode()就像是哈希函数,用来产生散列码,而equals()函数Map会用来判断当前的键是否与表中存在的键相同。

这就解释了我们上面两个问题,由于Java中所有的类都继承自Object基类,上面两个类也不例外,而在Object中默认是将对象的地址作为散列码的,这就导致了我们重新new了mc1后,虽然新的mc1所有数据与原mc1一模一样,但是两者的地址不同,所以我们在containKey中找不到mc1的原因了;而同样的equals()函数也继承自Object,默认的equals()函数比较的是两个对象的地址,而代码中虽然前后put到Map中的mc1是完全相同的,但是两者地址不同,这就造成了同样的MyClass对象mc1在Map中出现了两次。

在写equals函数时必须满足下列5个条件:

  1. 自反性。对任意x,x.equals(x)一定返回true
  2. 对称性。对任意x和y,如果y.equals(x)返回true,则x.equals(y)也返回true。
  3. 传递性。对任意x、y、z,如果有x.equals(y)返回true,y.equals(z)返回true,则x.equals(z)一定返回true。
  4. 一致性。对任意x和y,如果对象中用于等价比较的信息没有改变,那么无论调用x.equals(y)多少次,返回的结果应该保持一致。
  5. 对任何不是null的x,x.equals(null)一定返回false。

下面我们对上面的代码进行修改,让它达到我们想要的效果:

复制代码
 1 import java.util.HashMap;
 2 import java.util.Map;
 3 
 4 class MyClass {
 5     private static int counter = 0;
 6     public int id;
 7 
 8     public MyClass() {
 9         id = ++counter;
10     }
11 
12     @Override
13     public String toString() {
14         return "自定义类" + id;
15     }
16 
17     @Override
18     public boolean equals(Object obj) {
19         return this.id == ((MyClass) obj).id;
20     }
21 
22     @Override
23     public int hashCode() {
24         return id;
25     }
26 }
27 
28 public class TestMap {
29     public static void main(String[] args) {
30         Map<MyClass, String> testKey = new HashMap<MyClass, String>();
31         MyClass mc1 = new MyClass();
32         MyClass mc2 = new MyClass();
33         testKey.put(mc1, "NO.1");
34         testKey.put(mc2, "NO.2");
35         System.out.println(testKey);
36 
37         // equal()
38         mc1 = new MyClass();
39         mc1.id = 1;
40         testKey.put(mc1, "NO.3");
41         System.out.println(testKey);
42         System.out.println("***********");
43 
44         // hashcode()
45         mc1 = new MyClass();
46         mc1.id = 1;
47         System.out.println(mc1);
48         System.out.println(testKey);
49         System.out.println(testKey.containsKey(mc1));
50     }
51 }/*
52 {自定义类1=NO.1, 自定义类2=NO.2}
53 {自定义类1=NO.3, 自定义类2=NO.2}
54 ***********
55 自定义类1
56 {自定义类1=NO.3, 自定义类2=NO.2}
57 true
58 *///:~
复制代码

我在MyClass中重载了hashCode()和equals()方法,为了简单我就直接使用了id作为比较和散列码,这样结果就达到了我们预期的效果。

其实散列函数的选择还是比较复杂的,因为散列表中我们要尽可能少的造成冲突,这里我们就是针对特定情况采取了特殊的散列函数选取。从我平时编程的角度而言,将自定义类作为key值的情况还是比较少的,如果真的需要用到,在选取散列函数方面一定要考虑周全,要在简单和性能速度方面做出权衡。

转自:http://www.cnblogs.com/xltcjylove/p/3852404.html


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值