对重写Equals补充--Hashtable实现
昨天发了给你的类重写Equals--检测Class是否相等 ,得到了omnislash 的指点和启发,仔细查了一下.MSDN关于Equals的实现..上个文章中的例子,并不是很完整,当时也是急于说明问题和解决办法..具体的解决方案我会在随后的例子中提供...
既然需要再次重写Equals方法,今天就用Hashtable的实现,先说一下Hashtable,首先Hashtable保存的是以(键/值)为一对插入的..你可以方便的通过键来查找值,在国外的书中称为字典,他的作用也如其名一样..使用起来很类似字典..你知道一个字母a ,要查找对应字母a的值,可以这么写Hashtable["a"],这样就查到了对应a的值..但是需要注意一点,就是添加到Hashtable不能重复,必须有唯一性.并且因为Hashtable添加/删除是无序的,所以速度要快..就是因为它们是无序的,所以你不能通过索引,类似string[0].string[1]这样的方式访问到Hashtable中的数据.针对这样的情况,适应于下列地方,你知道key,但是value不确定,并且需要频繁的插入和删除操作,而这个时候数组和ArrayList就不是最佳的选择,因为在你获取某对象的时候,需要遍历..而Hashtable则比他们的效率要高..
下面是个简单的例子对ArrayList和Hashtable做了个对比:
上面的代码执行结果:
结果可以说明,使用正确的容器对程序的优化是至关重要的..而选择正确的容器,也完全在于对容器的了解和对业务的熟悉..这里需要注意一点,开头的地方说添加也很快,但是Hashtable的添加要比ArrayList慢很多..主要是因为Hashtable要对key做一系列的索引算法,也是为了更好的查找和删除做的准备工作... 另外这里提示一点,就是在Hashtable初始化的时候可以给它指定默认容量和加载因子,其中加载因子越小,则平均查找速度就越快,但内存消耗就越大..默认的容量是16,加载因子是1..加载因子的取值范围是0.1-1之间..可以根据你的数据大小适当的调整容量和加载因子来提高速度..
上面的代码执行结果:
如果你传递给Hashtable的key是Class,而如何保证key的唯一呢??因为Hashtable还是用到了HashCode做为验证唯一性,所以你还要在你的类覆写GetHashCode(),并且也要重写Equals()以判断是否相等..具体问题可以看给你的类重写Equals--检测Class是否相等 .基础的东西这里都有说明..尤其是后面的回复..可以多看看..
现在来看Hashtable的具体实现,首先你添加一对键/值后,会将键和对象的引用放入存储桶,Hashtable的键索引是按key对象的GetHashCode方法计算的,查找的时候也是用查找的键与存储的键对比,对比的方法还是要依赖Equals!!这个过程也说明了,为什么覆写了GetHashCode之后也要覆盖Equals了..看代码:
上面的代码实例化了四个类,其中两个小王,两个小李,先添加小李1和小王1到Hashtable中,然后用新的小王2和小李2进行查找Hashtable,因为作为键的User类重写了GetHashCode和Equals,并且实例的时候也都是用的小王和小李,并且他们的string.GetHashCode是一样的..所以会被找到...最后程序又试图添加小李2到Hashtable中,但是因为里面已经存在了一个相同的key.GetHashCode,所以添加导致了异常...上面的输出结果是:
如果对上面的解释不清楚,参考前两天的两个文章..
StringBuilder的实现与技巧
给你的类重写Equals--检测Class是否相等
既然需要再次重写Equals方法,今天就用Hashtable的实现,先说一下Hashtable,首先Hashtable保存的是以(键/值)为一对插入的..你可以方便的通过键来查找值,在国外的书中称为字典,他的作用也如其名一样..使用起来很类似字典..你知道一个字母a ,要查找对应字母a的值,可以这么写Hashtable["a"],这样就查到了对应a的值..但是需要注意一点,就是添加到Hashtable不能重复,必须有唯一性.并且因为Hashtable添加/删除是无序的,所以速度要快..就是因为它们是无序的,所以你不能通过索引,类似string[0].string[1]这样的方式访问到Hashtable中的数据.针对这样的情况,适应于下列地方,你知道key,但是value不确定,并且需要频繁的插入和删除操作,而这个时候数组和ArrayList就不是最佳的选择,因为在你获取某对象的时候,需要遍历..而Hashtable则比他们的效率要高..
下面是个简单的例子对ArrayList和Hashtable做了个对比:
1int i;
2System.Collections.Hashtable hs = new System.Collections.Hashtable();
3System.Collections.ArrayList arr = new System.Collections.ArrayList();
4
5long StartTime = DateTime.Now.Ticks;
6for ( i = 0 ; i < 10000 ; i ++ ) {
7 arr.Add(i);
8}
9long EndTime = DateTime.Now.Ticks;
10Console.WriteLine( "ArryList添加执行时间:" + (EndTime-StartTime) );
11
12StartTime = DateTime.Now.Ticks;
13for ( i = 0 ; i < 10000 ; i ++ )
14{
15 hs.Add(i,"我是"+i);
16}
17EndTime = DateTime.Now.Ticks;
18Console.WriteLine( "Hashtable添加执行时间:" + (EndTime-StartTime) );
19
20StartTime = DateTime.Now.Ticks;
21for ( i = 0 ; i < 10000 ; i ++ )
22{
23 arr.Contains(i) ;
24}
25EndTime = DateTime.Now.Ticks;
26Console.WriteLine( "ArryList查找执行时间:" + (EndTime-StartTime) );
27
28StartTime = DateTime.Now.Ticks;
29for ( i = 0 ; i < 10000 ; i ++ )
30{
31 hs.Contains(i) ;
32}
33EndTime = DateTime.Now.Ticks;
34Console.WriteLine( "Hashtable查找执行时间:" + (EndTime-StartTime) );
35
36StartTime = DateTime.Now.Ticks;
37for ( i = 0 ; i < 10000 ; i ++ )
38{
39 arr.Remove(i) ;
40}
41EndTime = DateTime.Now.Ticks;
42Console.WriteLine( "ArryList删除执行时间:" + (EndTime-StartTime) );
43
44StartTime = DateTime.Now.Ticks;
45for ( i = 0 ; i < 10000 ; i ++ )
46{
47 hs.Remove(i) ;
48}
49EndTime = DateTime.Now.Ticks;
50Console.WriteLine( "Hashtable删除执行时间:" + (EndTime-StartTime) );
2System.Collections.Hashtable hs = new System.Collections.Hashtable();
3System.Collections.ArrayList arr = new System.Collections.ArrayList();
4
5long StartTime = DateTime.Now.Ticks;
6for ( i = 0 ; i < 10000 ; i ++ ) {
7 arr.Add(i);
8}
9long EndTime = DateTime.Now.Ticks;
10Console.WriteLine( "ArryList添加执行时间:" + (EndTime-StartTime) );
11
12StartTime = DateTime.Now.Ticks;
13for ( i = 0 ; i < 10000 ; i ++ )
14{
15 hs.Add(i,"我是"+i);
16}
17EndTime = DateTime.Now.Ticks;
18Console.WriteLine( "Hashtable添加执行时间:" + (EndTime-StartTime) );
19
20StartTime = DateTime.Now.Ticks;
21for ( i = 0 ; i < 10000 ; i ++ )
22{
23 arr.Contains(i) ;
24}
25EndTime = DateTime.Now.Ticks;
26Console.WriteLine( "ArryList查找执行时间:" + (EndTime-StartTime) );
27
28StartTime = DateTime.Now.Ticks;
29for ( i = 0 ; i < 10000 ; i ++ )
30{
31 hs.Contains(i) ;
32}
33EndTime = DateTime.Now.Ticks;
34Console.WriteLine( "Hashtable查找执行时间:" + (EndTime-StartTime) );
35
36StartTime = DateTime.Now.Ticks;
37for ( i = 0 ; i < 10000 ; i ++ )
38{
39 arr.Remove(i) ;
40}
41EndTime = DateTime.Now.Ticks;
42Console.WriteLine( "ArryList删除执行时间:" + (EndTime-StartTime) );
43
44StartTime = DateTime.Now.Ticks;
45for ( i = 0 ; i < 10000 ; i ++ )
46{
47 hs.Remove(i) ;
48}
49EndTime = DateTime.Now.Ticks;
50Console.WriteLine( "Hashtable删除执行时间:" + (EndTime-StartTime) );
上面的代码执行结果:
1ArryList添加执行时间:0
2Hashtable添加执行时间:300432
3ArryList查找执行时间:10214688
4Hashtable查找执行时间:0
5ArryList查找执行时间:701008
6Hashtable查找执行时间:0
2Hashtable添加执行时间:300432
3ArryList查找执行时间:10214688
4Hashtable查找执行时间:0
5ArryList查找执行时间:701008
6Hashtable查找执行时间:0
结果可以说明,使用正确的容器对程序的优化是至关重要的..而选择正确的容器,也完全在于对容器的了解和对业务的熟悉..这里需要注意一点,开头的地方说添加也很快,但是Hashtable的添加要比ArrayList慢很多..主要是因为Hashtable要对key做一系列的索引算法,也是为了更好的查找和删除做的准备工作... 另外这里提示一点,就是在Hashtable初始化的时候可以给它指定默认容量和加载因子,其中加载因子越小,则平均查找速度就越快,但内存消耗就越大..默认的容量是16,加载因子是1..加载因子的取值范围是0.1-1之间..可以根据你的数据大小适当的调整容量和加载因子来提高速度..
1System.Collections.Hashtable hs = new System.Collections.Hashtable();
2int i;
3long StartTime = DateTime.Now.Ticks;
4for ( i = 0; i < 10000;i ++ )
5{
6 hs.Add( i,"我是"+i );
7}
8long EndTime = DateTime.Now.Ticks;
9Console.WriteLine( "hs添加执行时间:" + (EndTime-StartTime) );
10
11System.Collections.Hashtable hs2 = new System.Collections.Hashtable(10001,0.3f);
12StartTime = DateTime.Now.Ticks;
13for ( i = 0; i < 10000;i ++ )
14{
15 hs2.Add( i,"我是"+i );
16}
17EndTime = DateTime.Now.Ticks;
18Console.WriteLine( "hs2添加执行时间:" + (EndTime-StartTime) );
2int i;
3long StartTime = DateTime.Now.Ticks;
4for ( i = 0; i < 10000;i ++ )
5{
6 hs.Add( i,"我是"+i );
7}
8long EndTime = DateTime.Now.Ticks;
9Console.WriteLine( "hs添加执行时间:" + (EndTime-StartTime) );
10
11System.Collections.Hashtable hs2 = new System.Collections.Hashtable(10001,0.3f);
12StartTime = DateTime.Now.Ticks;
13for ( i = 0; i < 10000;i ++ )
14{
15 hs2.Add( i,"我是"+i );
16}
17EndTime = DateTime.Now.Ticks;
18Console.WriteLine( "hs2添加执行时间:" + (EndTime-StartTime) );
上面的代码执行结果:
1hs添加执行时间:200288
2hs2添加执行时间:100188
这个结果,经过我测试,在你i<40000的时候,是明显的,但是i>40000的时候,效率就很差了.甚至是不如默认的...并且根据数据的不同,这个结果都会有偏差,不过数据量保持在某个值(这个值也是很变化的)以下的时候,调整加载因子比默认的速度还是要快点的... 2hs2添加执行时间:100188
如果你传递给Hashtable的key是Class,而如何保证key的唯一呢??因为Hashtable还是用到了HashCode做为验证唯一性,所以你还要在你的类覆写GetHashCode(),并且也要重写Equals()以判断是否相等..具体问题可以看给你的类重写Equals--检测Class是否相等 .基础的东西这里都有说明..尤其是后面的回复..可以多看看..
现在来看Hashtable的具体实现,首先你添加一对键/值后,会将键和对象的引用放入存储桶,Hashtable的键索引是按key对象的GetHashCode方法计算的,查找的时候也是用查找的键与存储的键对比,对比的方法还是要依赖Equals!!这个过程也说明了,为什么覆写了GetHashCode之后也要覆盖Equals了..看代码:
1//============= 用户类 ==================
2public class User
3{
4 private string name = null;
5 public User(string name)
6 {
7 this.name = name;
8 }
9
10 public override string ToString() //覆写ToString()
11 {
12 return name;
13 }
14
15 //这里因为name是不重复的,所以获取name的HashCode,作为
16 public override int GetHashCode()
17 {
18 return ToString().GetHashCode();
19 }
20
21 /// <summary>
22 /// 覆写Equals,判断对象是否具有相同的
23 /// </summary>
24 /// <param name="obj"></param>
25 /// <returns></returns>
26 public override bool Equals(object obj)
27 {
28 User u = obj as User;
29 if ( u == null ) //如果不能转换为当前Class,则返回false
30 return false;
31 if ( this.name == u.name && this.ToString() == u.ToString() )
32 return true;
33
34 return false;
35 }
36
37}
38
39// ============ 对应用户的信息 ==============
40public class UserInfo
41{
42 private User u;
43 public UserInfo(User us)
44 {
45 this.u = us;
46 }
47
48 public override string ToString()
49 {
50 return u.ToString() + "的信息";
51 }
52
53}
54
55//========== 测试代码 Main ==================
56static void Main(string[] args)
57{
58 User u1 = new User("小李");
59 User u2 = new User("小王");
60 UserInfo info1 = new UserInfo(u1);
61 UserInfo info2 = new UserInfo(u2);
62 System.Collections.Hashtable hs = new System.Collections.Hashtable();
63
64 hs.Add( u1,info1 );
65 hs.Add( u2,info2 );
66
67 User u3 = new User("小李");
68 UserInfo info3 = new UserInfo(u1);
69 User u4 = new User("小王");
70
71 Console.WriteLine( hs[u3] );
72 Console.WriteLine( hs[u4] );
73 try {
74 hs.Add( u3, info3);
75 }catch(ArgumentException ee) {
76 Console.WriteLine( ee.Message );
77 }
78
79}
2public class User
3{
4 private string name = null;
5 public User(string name)
6 {
7 this.name = name;
8 }
9
10 public override string ToString() //覆写ToString()
11 {
12 return name;
13 }
14
15 //这里因为name是不重复的,所以获取name的HashCode,作为
16 public override int GetHashCode()
17 {
18 return ToString().GetHashCode();
19 }
20
21 /// <summary>
22 /// 覆写Equals,判断对象是否具有相同的
23 /// </summary>
24 /// <param name="obj"></param>
25 /// <returns></returns>
26 public override bool Equals(object obj)
27 {
28 User u = obj as User;
29 if ( u == null ) //如果不能转换为当前Class,则返回false
30 return false;
31 if ( this.name == u.name && this.ToString() == u.ToString() )
32 return true;
33
34 return false;
35 }
36
37}
38
39// ============ 对应用户的信息 ==============
40public class UserInfo
41{
42 private User u;
43 public UserInfo(User us)
44 {
45 this.u = us;
46 }
47
48 public override string ToString()
49 {
50 return u.ToString() + "的信息";
51 }
52
53}
54
55//========== 测试代码 Main ==================
56static void Main(string[] args)
57{
58 User u1 = new User("小李");
59 User u2 = new User("小王");
60 UserInfo info1 = new UserInfo(u1);
61 UserInfo info2 = new UserInfo(u2);
62 System.Collections.Hashtable hs = new System.Collections.Hashtable();
63
64 hs.Add( u1,info1 );
65 hs.Add( u2,info2 );
66
67 User u3 = new User("小李");
68 UserInfo info3 = new UserInfo(u1);
69 User u4 = new User("小王");
70
71 Console.WriteLine( hs[u3] );
72 Console.WriteLine( hs[u4] );
73 try {
74 hs.Add( u3, info3);
75 }catch(ArgumentException ee) {
76 Console.WriteLine( ee.Message );
77 }
78
79}
上面的代码实例化了四个类,其中两个小王,两个小李,先添加小李1和小王1到Hashtable中,然后用新的小王2和小李2进行查找Hashtable,因为作为键的User类重写了GetHashCode和Equals,并且实例的时候也都是用的小王和小李,并且他们的string.GetHashCode是一样的..所以会被找到...最后程序又试图添加小李2到Hashtable中,但是因为里面已经存在了一个相同的key.GetHashCode,所以添加导致了异常...上面的输出结果是:
1小李的信息
2小王的信息
3已添加项。字典中的键: “小李” 所添加的键: “小李”
2小王的信息
3已添加项。字典中的键: “小李” 所添加的键: “小李”
如果对上面的解释不清楚,参考前两天的两个文章..
StringBuilder的实现与技巧
给你的类重写Equals--检测Class是否相等