Java容器分析--Map

原文作者:Flyingis

原文地址:http://www.blogjava.net/flyingis/archive/2005/12/27/25534.html

标准的Java类库中包含了几种类型的Map,它们都拥有同样的基本接口Map,但是行为特性各不相同,主要表现在效率、键值对的保存、元素呈现次序、对象的保存周期和判定键是否等价的策略等方面。

1.Map 的功能方法
Map(interface): 维护 label value 的关联性,使得可以通过 label 查找 value
HashMap: Map 基于散列表的实现,取代了 Hashtable 。插入和查询 label/value 的开销是固定的,并且可以通过构造器设置容量和负载因子,以调整容器的性能。
LinkedHashMap: HashMap 的基础上做了一些改进,在迭代遍历它时,取得 label/value 的顺序是其插入的次序,或者是最近最少使用 (LRU) 的次序,速度上比 HashMap 要慢一点,但在迭代访问时速度会更快,主要原因是它使用了链表维护内部次序。
TreeMap: 查看 label label/value 时,元素会被排序,其次序由 Comparable Comparator 决定,因此查询所得到的结果是经过排序的。另外,它是唯一带有 subMap() 方法的 Map 具体类,即返回一个子树。它也是 SortedMap 接口的唯一实现, subMap() 方法也是从该接口继承的。
WeakHashMap: Weak Key 映射,允许释放映射所指向的对象。当映射之外没有引用指向某个 label 时,此 label 可以被垃圾收集器回收。
IdentityHashMap: 使用 == 代替 equals() label 进行比较的散列映射。
2.hashCode()
         当使用标准库中的类 Integer 作为 HashMap label 时,程序能够正常运行,但是使用自己创建的类作为 HashMap label 时,通常犯一个错误。
         HashMap 中通过 label 查找 value 时,实际上是计算 label 对象地址的散列码来确定 value 的。一般情况下,我们是使用基类 Object 的方法 hashCode() 来生成散列码,它默认是使用对象的地址来计算的,因此由第一个对象 new Apple(5) 和第二个对象 new Apple(5) 生成的散列码是不同的,不能完成正确的查找。通常,我们可以编写自己的 hashCode() 方法来覆盖基类的原始方法,但与此同时,我们必须同时实现 equals() 方法来判断当前的 label 是否与表中存在的 label 相同。正确的 equals() 方法满足五个条件:
(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) 多少次,返回的结果应该保持一致,要么一直是 true ,要么一直是 false
(5)     对任何不是 null x x.equals(null) 一定返回 false
equals() 比较的是对象的地址,如果要使用自己的类作为 HashMap label ,必须同时重载 hashCode() equals() 方法。
使用散列的目的:想要使用一个对象来查找另一个对象。使用 TreeSet TreeMap 也能实现此目的。另外,还可以自己实现一个 Map ,此时,必须提供 Map.entrySet() 方法来生成 Map.Entry 对象的 Set
使用散列的价值:速度,散列使得查询可以快速进行。散列将 label 保存载数组中方便快速查询,因为存储一组元素最快的数据结构是数组,用它来表示 label 的信息 ( 后面有信息的描述 ) ,而不是 label 本身。通过 label 对象计算得到一个数字,作为数组的下标,这个数字就是散列码 ( 即前面所述的信息 ) 。该散列码具体是通过定义在基类 Object 中,可能由程序员自定义的类覆盖的 hashCode() 方法,即散列函数生成。为了解决数组容量带来的限制,可以使不同的 label 生成相同的下标,保存在一个链表 list 中,每一个链表就是数组的一个元素。查询 label 时就可以通过对 list 中的信息进行查找,当散列函数比较好,数组的每个位置中的 list 长度较短,则可以快速查找到数组元素 list 中的某个位置,提高了整体速度。
散列表中的 slot 通常称为 bucket ,为了使散列分步均匀, bucket 的值一般取质数。但事实证明,质数实际上并不是散列 bucket 的理想容量,近来 Java 散列实现都使用 2 的幂,具体如何验证以后再续。
3.HashMap 的性能因子
容量 (capacity): 散列表中 bucket 的数量。
初始化容量 (initial capacity): 创建散列表时 bucket 的数量。可以在构造方法中指定 HashMap HashSet 的初始化容量。
尺寸 (size): 散列表中记录的数量。 ( 数组的元素个数,非 list 中元素总和 )
负载因子 (load factor): 尺寸 / 容量。负载因子为 0 ,表示空的散列表, 0.5 表示半满的散列表。轻负载的散列表具有冲突少,适宜插入与查询的特点,但是使用迭代器遍历会比较慢。较高的负载会减少所需空间大小。当负载达到指定值时,容器会自动成倍地增加容量,并将原有的对象重新分配,存入新的 bucket 中,这个过程称为“重散列”。
4. 重写 hashCode() 的关键
(1)     对同一个对象调用 hashCode() 都应该生成同样的值。
(2)     hashCode() 方法不要依赖于对象中易变的数据,当数据发生变化时, hashCode() 就会生成一个不同的散列码,即产生了一个不同的 label
(3)     hashCode() 不应依赖于具有唯一性的对象信息,例如对象地址。
(4)     散列码应该更关心速度,而不是唯一性,因为散列码不必是唯一的。
(5)     好的 hashCode() 应该产生分步均匀的散列码。在 Effective Java(Addison-Wesley 2001) 中, Joshua Bloch hashCode() 给出了设计指导,可以参考。

编写正确高效的hashCode()equals()可以参考ApacheJakarta Commons项目中的工具。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值