基础知识提问:关于HashTable和List两个容器Add改变了属性的同一对象的问题

今天中午在北京博客园俱乐部中聊天时子秋同学的提问:

提问, 基础知识: 一个自定义类型t1, 一个集合list, list.add( t1 )后, 更改t1的属性name的值, 再list.add( t1 ). list中的两个值是否相等?此时list中是一个值还是两个值?

脑袋兄马上给出了回答:

1.相等,2. 两个值

且不说脑袋兄的答案是否正确,先摘录对话内容如下:

子秋 说:
提问, 基础知识: 一个自定义类型t1, 一个集合list, list.add( t1 )后, 更改t1的属性name的值, 再list.add( t1 ). list中的两个值是否相等?
子秋 说:
此时list中是一个值还是两个值.

Fan Shi(装配脑袋) 说:

1.相等,2. 两个值

子秋 说:
脑袋基础真好.

陈红卫 说:
这个要看你ADD了两次还是一次。
。。。。。[此后省略若干千字聊天记录]

我搬了个小板凳,坐着观看各位兄弟就此问题在争论。此时我并不太十分确定脑袋的答案是正确的,因为没太注意List的内部实现,所以也不敢乱说话。虽然我前段时间研究过Java内部的HashMap的源码,但是也不敢就此断定一定是相等或者一定是不相等。后来脑袋兄弟给出了用VB10写的程序如下:

Dim a = New With {.Name = "Harry" }
Dim l = { a }.ToList()
a.Name = "Potter"
l.Add( a)

……

当时我并没有.NET开发环境,所以也就没发表意见。后来扯着扯着又扯到HashTable中去了,最后得到结论是“两个值相当, 都是修改后的t1属性”,聊天暂时告一段落。

任何没有理论作依据,或者没有实践就得出结论者是对他人也是对自己的不负责任。鉴于此,回来之后简单地看了List和HashTable的内部实现,发现其内部容器及其Add方法的逻辑有较大的区别。

1.List

List<T>的内部容器为类型T的数组,如下

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--> private T[]_items;

Add方法如下:

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--> public void Add(Titem){
if (_size == _items.Length)EnsureCapacity(_size + 1 );
_items[_size
++ ] = item;
_version
++ ;
}

由此看来,无论你Add进的对象的某一字段值是否发生了变化,它内部数组均无条件地保存进去你Add进去的对象T,并且维护的均是指向该对象的同一个引用,同时在获取其Count时返回的其实是你调用Add方法的次数,即其内部的_size字段:

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--> public int Count{
get { return _size;}
}

由此便不难回答开头子秋的那个问题的答案为什么会是“两个值,相等”了。

2. HashTable

当时聊天时我提出了这样的一个问题“如果将一个重写了HashCode和Equals方法的对象作为key放到HashTable中会出现什么现象?”之所以提出这样的问题,是因为前段时间生产机上出现了一个莫名其妙的问题,害得我花了将近一天的时间才找到原来是公司的前辈们写的持久层框架中的Schema用了不正确的重写equals方法导致的,当时郁闷到差点吐血。

针对我提出的问题,脑袋兄给出的解答如下:如果对象的hashcode会变,它就不适合放到hashtable里。

先来看HashTable的内部容器,其实是一个结构体,如下:

Code
<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />-->//Thehashtabledata.
//Thiscannotbeserialised
privatestructbucket{
publicObjectkey;
publicObjectval;
publicinthash_coll;//Storehashcode;signbitmeanstherewasacollision.
}

其中has_coll的作用为维护key的hashCode,在调用Add方法时,会先判断在该结构体类型bucket[]的数组buckets中是否已经存在该key的hashCode的元素,如果不存在则key和value同时放进该结构体数组,如果存在则抛出“已添加此项”的异常,即不允许添加重复健值的项。

如果按照如下方式重写了hashCode和equals方法:

Code
<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />-->classCustomer
{
publicStringName{get;set;}

publicoverrideintGetHashCode()
{
returnName.GetHashCode();
}

publicoverrideboolEquals(objectobj)
{
return((Customer)obj).Name==this.Name;
}
}

那么在将该对象的Name改变一个值之后再向HashTable中作为键添加,不会抛异常。

根据前面的HashTable内部容器Struct可很容易地看出来,HashTable判断某一对象是否已经存在就是通过key的HashCode是否已经存在于它内部维护的这个struct类型的数组中,如果根据我上面说那种方式去重写hashCode和Equals方法,那么在该对象的Name属性变化时就得到的HashCode值也不一样,此时放到HashTable中时就不会认为是一个已经存在的对象,但是改变值之前的放进到HashTable中的对象无法通过key再索引到了,也就是它丢失了(不过如果你将该对象的Name值再改变回原来的值就又可以找到了……)

对于HashCode有可能改变的对象,是不适合放到HashTable中的。

经常看看基础,其实对于我们提高很有益。

以上如有不对的地方,欢迎拍砖指正。

欢迎更多朋友加入北京博客园俱乐部

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值