王朝网络
分享
 
 
 

了解实际开发中 Hashtable 的特性原理 .NET, JAVA, PHP (之二)

王朝php·作者佚名  2006-12-20
宽屏版  字体: |||超大  

[文档最后修正时间: Aug 19, 2006, by Arturya]

上一篇谈到 .NET 的 Hashtable 属于比较传统的算法. 并籍此复习了哈希表这种数据结构的经典原理. 下面我们来看看 Java 和 PHP 中又是如何实现 Hashtable 的. 之所以把 Java 和 PHP 的场景结合一起, 是因为他们俩的处理方式非常相似. 论述将以 java.util.HashMap 为主, 该原理同样也适于 PHP. HashMap 是 java.util.Hashtable 的轻量级实现, 且允许 NULL 作为关键字.

通过前文, 我们已知由于 .NET Hashtable 哈希函数 f(K) 的取模运算特征决定了其容量大小必然是某个质数 (若不明白请回顾第一篇). 而 Java HashMap 则恰恰相对, 她们要求哈希表的容量是一个偶数, 且为 2 的 N 次幂. 即 Array.Length = 2, 4, 8, 16, 32, 64... (转化为二进制恰好为 1111 ... 110 形式). 这是由于 Java HashMap 的哈希算法核心为与 (&) 运算, f(K) = HashOf(K) & (Array.Length - 1), 运算时 HashOf(K) 的高位同 0 相与被丢弃, 低位同 1 相与被保留, 以次保证 0 <= f(K) < Array.Length.

HashMap 的冲突处理方式也区别于 .NET Hashtable 的开放定址法, 为 "链地址法". 当后插入的 K2, K3, K4 等与先插入的 K1 位置冲突时, 在 K1 位置建立一个链表, 把 K2, K3, K4... 另存至链表中, 即一个位置可以对应 "一串" 数据. 搜索 K2, K3, K4 的时候先查找 f(K1) 位置本身, 若不符则顺序遍历链表. 这样处理的好处是不会产生如同 .NET Hashtable 中开放定址法的二次聚集现象 (在探测空位时产生连续冲突).

HashMap 也存在一个 Load Factor, 其值为 0.75. 我们看到 HashMap 的冲突处理方式不会产生二次聚集, 因此装填因子可以适当放大一些. 其初始默认容量为 16, 当实际装入数据和容量之比超过 0.75 时开始自动扩容, 新的容量为原始容量的 2 倍 (32, 64, 128... 依此类推). 乘 2 运算通过左移直接实现, 没有 .NET 那般运算判断质数的困扰.

PHP 的 Hashtable 构造和 Java HashMap 基本相通, 只是 Load Factor 值为 1.

从理论上来说, .NET Hashtable 的平均搜索步长约为 -ln(1-x)/x, 而 Java HashMap 的平均搜索步长约为 1 + x/2, 这里 x 为实际装入数据个数和容量之比, 由此看出一个哈希表的平均查找长度为 x 的函数, 且 0 <= x < Load Factor, 而非插入数据个数 N 的函数, 因此她的查找复杂度为 O(1).

从实际运算来看, 性能评估是比较复杂的事情, 不仅仅取决于理论搜索步长, 还取决于实际 Load Factor 的取值, HashOf(K) 的效率, Resize() 的效率, 探测数组和拆链装链的效率, 甚至函数的压参传值这种微小开销累积起来也对总体性能有可观的影响. 在 C# 中仿照 Java HashMap 简单写了一个采用链地址法的哈希表, 初步测试显示其与 .NET 原始的 Hashtable 相比读取速度较快但写入较慢, 互有胜负. 考虑到 Hashtable 还有线程安全处理, 慢一点可以理解, 情况要比想象的好.

下面我们从 Java 回到 .NET, 介绍一下 2.0 新推出的 System.Collections.Generic.Dictionary<K, V> 类型 (以下简称 Dictionary).

Dictionary 的用法与 Hashtable 差不多, f(K) 也采用相同的取模算法, 但其余内部结构无论同 Hashtable 还是 HashMap 都有很大区别. 体现在:

[1] Dictionary 没有 Load Factor 变量, 或可理解为 Load Factor = 1, 插入数据可以最大限度填满容量空间.

[2] 大体上, Dictionary 内的元素有按照先后插入顺序排列的特性. 区别于 Hashtable 及 HashMap 的离散型.

Dictionary 内部拥有两列数组, 姑且成为 "状态串" 和 "数据串", 数据串按顺序接受插入的数据并加以封装 (封装了一个 next 指针, 方便今后装链), 状态串以 f(K) 为索引, 存放 f(K) 到 K 在数据串内的位置映射. 查找时先访问状态串找 f(K), 然后根据映射关系找到数据串中的实际数据. 当有多个 f(K) 重复时, 状态串里面保留第一个 f(K), 在数据串内对冲突的数据建立链关系.

由图我们可以看出, 数据串忠实按照 K1, K2, K3 ... 的插入顺序排列, 直至排满数据串, 这个过程中自然不会发生冲突, 冲突只出现在状态串中, 同时, 发生冲突时并不需探测空位和挪动数据本身, 只需将冲突的数据链在一起即可.

Dictionary 默认初始大小为 3, 扩容方式和 Hashtable 一样, 为不小于当前容量的 2 倍的一个质数. 在速度方面, Dictionary 读取快于 Hashtable, 但不如用 C# 自写的链地址法的哈希表, 写入要慢于 Hashtable, 但快于链地址法的哈希表. 考虑到 Dictionary 隐含的装填因子为 1, 可以最大限度利用空间, 是一种以速度换空间的做法, 这个结果是可以接受的.

由于 Hashtable 和 Dictionary 同时存在, 在使用场景上必然存在选择性, 并不任何时刻都能相互替代.

[1] 单线程程序中推荐使用 Dictionary, 有泛型优势, 且读取速度较快, 容量利用更充分.

[2] 多线程程序中推荐使用 Hashtable, 默认的 Hashtable 允许单线程写入, 多线程读取, 对 Hashtable 进一步调用 Synchronized() 方法可以获得完全线程安全的类型. 而 Dictionary 非线程安全, 必须人为使用 lock 语句进行保护, 效率大减.

[3] Dictionary 有按插入顺序排列数据的特性 (注: 但当调用 Remove() 删除过节点后顺序被打乱), 因此在需要体现顺序的情境中使用 Dictionary 能获得一定方便.

以上分别谈到了 Hashtable, HashMap 和 Dictionary 三种类型, 介绍告一段落, 下面增加一些不成熟的观点:

[1] 三种哈希表均允许任意 object 做关键字, 但实际使用中我们一般只用 string 做键值, 对 string 做 HashOf(string) 处理比较单纯, 速度较快, 而对 object 取 HashOf(object) 则情况复杂. 若想进一步提高速度, 可以考虑自定义一个只允许 string 作为关键字的哈希表.

[2] Java HashMap 由于 f(K) 取与运算的特性, 每次扩容必须是 2 倍, 没有价钱可讲. 但 .NET Hashtable 和 Dictionary 的容量理论上只要求是质数, 新容量不一定要达到旧容量的 2 倍以上, 因而想进一步提高内存利用率, 可以考虑重写 Resize() 方法, 使得每次扩容变成稍大一点的质数即可. 当然这样一来插入效率会降低, 自行取舍.

[3] 对 Hashtable 初始化时直接指定 capacity 是个好主意, 减少了 Resize() 的次数, 降低开销.

第一篇地址: http://blog.csdn.net/arturya/archive/2006/08/19/1096139.aspx[url=http://blog.csdn.net/arturya/archive/2006/08/18/1091110.aspx][/url]

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
>>返回首页<<
推荐阅读
 
 
频道精选
 
静静地坐在废墟上,四周的荒凉一望无际,忽然觉得,凄凉也很美
© 2005- 王朝网络 版权所有