集合操作总结日志
1.集合: 命名空间System.Collection
a) 集合常用操作: 增删改查 遍历 来自于IList接口
b) 就是一个能装一堆东西的容器. 而变量是一个能装一件东西的容器
i. ArrayList arr=new ArrayList(); 可变长度数组,使用类似于数组, 在添加第一个 元素 的时候 ,给数组扩容默认4个长度,再以后每一次添加元素超过数组长度时,就将实际添加数量赋给数组的长度.(比如数组长度现在是4,你要添加6个元素,这样一共为10个元素,数组这是不是扩容2次变为16的长度,而是只扩容一次,然后将元素的实际长度赋给数组的长度)
1. 属性: Capacity:总容量 Count:数量
2. AddRange(ICollection );实现原理: 先扩容,然后调用 Array.CopyTo();方法 存入新数据.
3. arr.Capacity=200; 创建一个200大小的数组 然后用新数组 替换 旧数组.
4. Remove() 是根据引用来删除元素的. Remove()中查找删除对象时的比较方法是Object里 默认的的Equals,比较方式是比较的 地址. 可以用户自定义比较类型 重写Remove()方法.
5. ToArray(); 直接获得ArrayList 中的数组 返回一个Object[]数组
6. Hashtable(key,value): Hashtable里的键不可以重复, 存储键的时候通过hashcode 查找存储地址,从而找到对应的值.
a) Hash算法:能够快速定位对象. 按照Hash码取值
b) (存)当我们向Hashtable中Add元素时,元素储存在Hashtable的数组里的,下标是根据添加key的hash值算出来的(但因为hash值 取余 arr.Length(数组长度),所以肯定不会超过当前数组长度) !!! Hashtable 储存元素时,是根据key的Hash值(hash值是通过GetHashCode()算出来的) %(取余) 数组长度 求出的一个下标值
1.获得key的哈希值.
2.用哈希值取余数组长度 获得一个下标index
3.创建一个bucket对象,并设置3个成员值,添 加到index位置
4.注意:每个对象算出的Hashcode并不是唯一 的,有可能出现多个对象的Hashcode相同.
a) 解决机制: 1.再次hash一次. 2.桶装模式,将两个相同hashcode的对象装入同一位置.
5.当新增时,Hashtable里的容器数组已经满了,则以数组两倍的长度扩容.
(取) : 当我们从Hashtable取元素时(根据key来取),会根据key的hash值算出 对应要取元素的下标,并且比较元素里的key和当前要找的key参数的hash值是否相等,同时还要比较两个key的引用是都一致,如果都满足,则确定找到要取的元素
总结: 存 : 会根据key的 hash值 算出一个下标, 然后存
取 : 会根据key的 hash值,算出一个下标, 然后取
取值得时候不需要遍历, 算出来下标直接取.
哈希表Hashtable实现原理:Hashtable中的实际数据都存储在一个内部的buket[]数组中,当用户希望取得Hashtable值得时候,Hashtable进行如下处理:
1.为了保证取值范围保存在 0--Buket.Length之间, 首先哈希算法根据键key 与数组长度Length 进行取模运算,算的实际数据的位置为: (f(K)=HashOf(K) % Array.Length) 至于这个哈希函数Hash(f)怎么算出来的,简单说,可以取关键字的AscII码,根据一定规则运算得到.
2.如果发现多个Key值得哈希值重复,如果发生多个 K 值的哈希值重复, 即 f(K1) = f(K2), 而 f(K1) 位置已经有数据占用了, Hashtable 采用的是 "开放定址法" 处理冲突, 具体行为是把 HashOf(K2) % Array.Length 改为 (HashOf(K2) + d(K2)) % Array.Length , 得出另外一个位置来存储关键字 K2 所对应的数据, d 是一个增量函数. 如果仍然冲突, 则再次进行增量, 依此循环直到找到一个 Array 中的空位为止. 将来查找 K2 的时候先搜索 HashOf(K2) 一档, 发现不是 K2, 那么增量 d(K2) 继续搜索, 直到找到为止. 连续冲突次数越多, 搜索次数也越多, 效率越低.
3.当插入的数据达到Hashtable容量的上限时,对 内部buket数组进行扩容(重新new一个更大的数组,然后把数据copy过去,最后将新数组的地址引用再付给旧数组)
4.随着插入的数据项逐渐增多, Hashtable 内部数组剩余的空位也越来越少, 下一次冲突的可能性也越来越多严重影响效率. 因此不能等到数组全部塞满后才进行扩容处理. 在 .NET 中, 当插入数据个数和数组容量之比为 0.72 时, 就开始扩容. 这个 0.72 称为装填因子 - Load Factor. 这是一个要求苛刻的数字, 某些时刻将装填因子增减 0.01, 可能你的 Hashtable 存取效率就提高或降低了 50%, 其原因是装填因子决定 Array.Length, Array.Length 影响 f(K) 的冲突几率, 进而影响了性能. 0.72 是 Microsoft 经过长期实验得出的一个比较平衡的值. (取什么值合适和 f(K) 的算法也有关, 0.72 不一定适合其他结构的哈希表)
5.Hashtable 的初始容量 Array.Length 至少为 11, 再次扩容的容量至少为 "不小于 2 倍于当前容量的一个质数". 这里举一个例子, 方便大家看看 Hashtable 是多么浪费空间.
假设以默认方式初始化一个 Hashtable, 依次插入 8 个值, 由于 8 / 0.72 > 11, 因此 Hashtable 自动扩容, 新的容量为不小于 11 * 2 的质数, 即 23. 所以, 实际仅有 8 个人吃饭, 却不得不安排一桌 23 个座儿的酒席, 十分奢侈. 避免如此铺张的途径是在初始化 Hashtable 时用带参构造方式直接指定 capacity 为 17, 但即便这样仍浪费了 9 个空间.
有心的读者经过计算, 可能会问为什么不是指定初始容量为 13, 13 是质数啊, 13 * 0.72 > 8 啊. 确实理想情况是这样, 但实际上由于动态计算并判断一个数是否质数需要大量时间, 故 .NET Hashtable 中的 capacity 值是内部预设的一个数列, 只能为 3, 7, 11, 17, 23... 所以十分遗憾. (注: 只有当 Array.Length > 0x6DDA89 时动态计算扩容容量, 正常情况下我们不会存如此多的数据进去)
.NET 的 Hashtable 就是以这种方式来减少冲突, 以牺牲空间为代价换取读写速度. 假设你在实际开发中对内存空间要求很敏感, 譬如开发 ASP.NET 超大型 B/S 网站时, 就十分有必要检讨使用 Hashtable 的场景需求, 有的时候能否换个方式, 采取自定义 struct, 或者数组来高效实现呢?