java集合框架学习笔记 Map(映射)及其实现类

前面,我们简要地讲述了Collection三个子接口List、Set和Queue,同时还对它们的代表实现类做了一番探究。下面,我们将讲述另一个根接口Map及其实现类HashMap、TreeMap和Hashtable。
        首先,我们来看一下Map框架:


        Map映射是用来存放键/ 值对的,实现如果提供了键, 就能够快速查找到值需求。一个映射不能包含重复的键,且每个键最多只能映射到一个值。Java 类库为映射提供了两个通用的实现:HashMap 和TreeMap。散列映射对键进行散列, 树映射用键的整体顺序对元素进行排序, 并将其组织成搜索树。散列或比较函数只能作用于键。与键关联的值不能进行散列或比较。那么我们应该选择散列映射还是树映射呢? 与集一样, 散列稍微快一些, 如果不需要按照排列顺序访问键, 就最好选择散列。

        由于前面Set已经简单介绍过散列与树,在这里便不再赘述,若想详细探究散列与树的知识,我们后面将会开辟一系列关于常用算法的文章,可以参考一下)下面我们将简要说下HashMapTreeMap。(具体API见连接文档)

        HashMap是基于哈希表的 Map 接口的实现。它不能保证映射的顺序,特别是也不能保证现在顺序恒久不变。HashMap假定哈希函数将元素适当地分布在各桶之间,可为基本操作(get 和 put)提供稳定的性能。迭代 collection 视图所需的时与 HashMap 实例的“容量”(桶的数量)及其大小(键-值映射关系数)成比例。所以,如果迭代性能很重要,则不要将初始容量设置得太高(或将加载因子设置得太低)。

        /**下面解释下初始容量和加载因子:容量是哈希表中桶的数量,初始容量只是哈希表在创建时的容量。加载因子是哈希表在其容量自动增加之前可以达到多满的一种尺度。当哈希表中的条目数超出了加载因子与当前容量的乘积时,则要对该哈希表进行 rehash操作(即重建内部数据结构),从而哈希表将具有大约两倍的桶数。

       通常,默认加载因子(0.75)在时间和空间成本之间提供了良好的权衡。较高的值会减少空间开销,但会增加查找成本(反映在HashMap类的大多数操作中,包括 get和put)。在设置其初始容量时,应考虑映射中的预期条目数及其加载因子,以便最小化重新散列操作的数量。如果初始容量大于最大条目数除以加载因子,则不会发生rehash操作。如果要将多个映射存储在HashMap 实例中,则使用足够大的容量创建映射将允许映射更有效地存储,而不是根据需要执行自动重新散列来扩展表。请注意,使用具有相同键的许多键hashCode()是降低任何哈希表性能的可靠方法。

        */

       TreeMap是基于红黑树(Red-Black tree)的NavigableMap实现。该映射根据其键的自然顺序进行排序,或者根据创建映射时提供的 Comparator进行排序,具体取决于使用的构造方法。根据此算法实现的TreeMap为 containsKey、getput 和 remove 操作提供了受保证的 log(n) 时间开销。

      下面我们补充一个概念:视图(view)-实现了Collection 接口或某个子接口的对象(个人认为是一种提供了便于对进行操作的“集合”或“迭代器”),看似一个集合的“副本”,但当对视图做出修改时又会反过来影响到原集合,反之亦然(视图设计初衷便只提供对元素进行查改,而不提供删改的方法,以便轻量操作)。同时集合还提供了多种视图,如只读视图、子范围视图,同步视图和检查视图等。关于视图,在Map中得到了很好的体现,(javaFX的 ObserveableList更为明显)

       Map提供了3 种视图: 键集、值集合(不是一个集而为Collection) 以及键/ 值对集。其中键和键/ 值对可以构成一个集, 因为映射中一个键只能有一个副本。下面是他们的方法:(需要说明的是, keySet 不是HashSet 或TreeSet, 而是实现了Set 接口的另外某个类的对象)

       Set<K> keySet();

       Collection<V> values();

       Set<Map.Entry<K, V>> entrySet();

      那么当我们想对键、值或某一键值对做出某些修改时,我们便可以通过获得它的视图,轻量操作。例如,当我们想访问Map中所有值时便可以通过视图快速操作。而不是对Map进行操作。

下面是访问所有值的高效方案:

       Collection<String> values= map.values();
       for (String value: values){
           system.out.println(value);
       }

       最后,我们分享两个有趣的Map:WeakHashMap及LinkedHashMap。

       设计WeakHashMap 类是为了解决内存泄露问题,这样的情况常常出现,当Key对象的引用不再存在时,而Map却仍会保留这一对K-V对,而且永远将无法被删除,除非销毁整个Map。这样便导致垃圾回收器也无法起到作用,造成内存泄露。因此, 需要由程序负责从长期存活的映射表中删除那些无用的值。或者使用WeakHashMap 完成这件事情。当对键的唯一引用来自散列条目时, 这一数据结构将与垃圾回收器协同工作一起删除键/ 值对。

       下面是这种机制的内部运行情况。WeakHashMap 使用弱引用( weak references) 保存键。WeakReference 对象将引用保存到另外一个对象中, 在这里, 就是散列键。对于这种类型的对象, 垃圾回收器用一种特有的方式进行处理。通常,如果垃圾回收器发现某个特定的对象已经没有他人引用了,才会将其回收。然而,如果某个对象只由WeakReference 引用, 垃圾回收器也将会回收它,但会将引用这个对象的弱引用放入队列中。WeakHashMap将会周期性地检查队列,找出队列中新添加的弱引用。因为一个弱引用进人队列意味着这个键不再被他人使用, 并且已经被收集起来。于是,WeakHashMap 将删除对应的条目。

       而LinkedHashMap 类用来记住插人元素项的顺序。这样就可以使得在散列表中的项从表面上看是有序的,(基于双向链表原理),每当条目插入到表中时,就会并入到双向链表中,以能够以某种顺序遍历。(注意:链接散列映射作用于访问顺序, 而不是插入顺序, 对映射条目进行迭代。每次调用get 或put, 受到影响的条目将从当前的位置删除, 并放到条目链表的尾部(只有条目在链表中的位置会受影响, 而散列表中的桶不会受影响。一个条目总位于与键散列码对应的桶中))。

       这一实现类在LRU算法中得到充分利用,例如, 可能希望将访问频率高的元素放在内存中, 而访问频率低的元素则从数据库中读取。当在表中找不到元素项且表又已经满时, 可以将迭代器加入到表中, 并将枚举的前几个元素删除掉。这些是近期最
少使用的几个元素。甚至可以让这一过程自动化。即构造一LinkedHashMap 的子类,然后覆盖下面这个方法:protected boolean removeEldestEntry(Map.Entry<K, V> eldest);(当其返回true时LinkedHashMap便会删除最“旧”的元素)

       那么,今天关于ava集合框架之Map(映射)的介绍就到这里了,在本文中,我们简要地介绍了关于Map的要点,并就此展开HashMap和TreeMap实现原理、特点及使用注意要点;然后,附着介绍了java集合中的视图概念及其使用场景;最后我们简单介绍了WeakHashMap及LinkedHashMap的实现原理和适用场景,便于扩展我们的视野。

       下一篇,我们将简单介绍javafx的工具类(简要介绍下其实现的算法),同时对这一系列文章做个总结。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值