zoukankan      html  css  js  c++  java
  • 源码阅读(15):Java中主要的Map结构——概述

    (接上文《源码阅读(14):Java中主要的Queue、Deque结构——PriorityQueue集合(下)》)

    1、概述

    1.1、Map结构和Set集合的关系

    为什么要先介绍Java中的主要Map结构呢?如果读者是从本专题第一篇文章开始阅读的话,那么就应该清楚目前我们整个专题还在介绍Java中java.util.Collection接口的注意要实现集合 ,具体来说就应该是List集合、Queue/Deque集合以及Set集合。那么List集合、Queue/Deque集合介绍完后,理所当然就应该开始介绍Set集合。下图为Java中重要的Set集合的构建体系:
    在这里插入图片描述
    是的,从最直观的讲解思路考虑,整个专题是应该按照这样的思路进行介绍。但事实上Java中重要的Set集合内部实现全部基于对应的Map<K , V>结构。举例来说,在早期Java中版本中广泛使用的HashSet集合其内部是一个HashMap,代码片段如下所示:

    public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, java.io.Serializable {
      // ......
      private transient HashMap<E,Object> map;
      // Dummy value to associate with an Object in the backing Map
      private static final Object PRESENT = new Object();
      /**
       * Constructs a new, empty set; the backing <tt>HashMap</tt> instance has
       * default initial capacity (16) and load factor (0.75).
       */
      public HashSet() {
        map = new HashMap<>();
      }
      // ......
      public HashSet(int initialCapacity) {
        map = new HashMap<>(initialCapacity);
      }
      // ......
    }
    

    再例如Java中另一个Set集合TreeSet,其内部使用的是TreeMap结构。再例如线程安全的跳跃表结构ConcurrentSkipListSet,内部实际上使用的是ConcurrentSkipListMap。甚至,第三方类org.eclipse.jetty.util.ConcurrentHashSet内部也是使用的java.util.concurrent.ConcurrentHashMap。所以基本上可以说,如果搞清楚了Java中重要的Map结构实现,那么就搞清楚了Java中重要的Set集合实现。

    1.2、Map结构概述

    1.2.1、基本结构

    首先Map结构同样属于Java Collections Framework的知识范畴,但是代表Map结构的顶级接口java.util.Map并没有继承java.util.Collection接口。这是因为Map结构属于映射式容器,既是一个Key键对应一个Value值(所以称为键值对),且同一个容器中不能出现两个相同的Key键信息。

    An object that maps keys to values. A map cannot contain duplicate keys;each key can map to at most one value.
    在这里插入图片描述

    上图所示的Map主要结构体系中,我们将重点介绍java.util包中的TreeMap容器、HashMap容器和LinkedHashMap容器。其中TreeMap容器基于红黑树进行构造,HashMap容器和LinkedHashMap容器基于数组+链表+红黑树的复合结构进行构造,而后两中容器的区别仅体现在HashMap容器中的数组被替换成了链表。

    另外,ConcurrentHashMap容器和ConcurrentSkipListMap容器也是Map结构体系下重要的线程安全的容器,我们将在本专题后续的专门介绍java.util.concurrent包的文章中专门进行介绍。其中基于跳跃表结构进行构建的ConcurrentSkipListMap容器尤为重要。

    1.2.2、键值对定义方式Entry

    上文已经提到,Map容器中存储的是键值对,既是一个键信息和一个值信息的映射关系。Map容器中可以有成千上万的键值对信息,每一个键值对都使用Map.Entry<K , V>的定义进行存储——也就是说一个Map容器中可以有成千上万个Map.Entry接口的实例化对象

    在这里插入图片描述

    Map.Entry<K , V>接口的主要代码如下所示:

    public interface Map<K,V> {
      // ......
      interface Entry<K,V> {
        // 获取当前Entry表示的键值对的键信息
        K getKey();
        // 获取当前Entry表示的键值对的值信息
        V getValue();
        // 设定当前Entry表示的键值对的值信息
        V setValue(V value);
        // 比较两个键值对是否相同
        boolean equals(Object o);
        // 求得当前键值对的hash值
        int hashCode();
        // ......
      }
      // ......
    }
    

    实际上从JDK1.8+开始,Map.Entry接口中还有一些其它定义,这里为了讲解方便我们暂时不去涉及,后续的内容中会逐步进行说明。一般来说实现了Map接口的具体实现类,都会根据自己的结构特定实现Map.Entry接口。例如AbstractMap类中就有AbstractMap.SimpleEntry类实现了Map.Entry接口;HashMap类中就有HashMap.Node类实现Map.Entry接口;TreeMap类中就有TreeMap.Entry类实现Map.Entry接口……
    在这里插入图片描述
    这些Map.Entry接口的具体实现类,都根据自己存储键值对的特性做了不同的扩展,例如我们可以看一下TreeMap.Entry中的构造定义:

    public class TreeMap<K,V>
        extends AbstractMap<K,V>
        implements NavigableMap<K,V>, Cloneable, java.io.Serializable {
      // ......
      /**
       * Node in the Tree.  Doubles as a means to pass key-value pairs back to
       * user (see Map.Entry).
       */
      static final class Entry<K,V> implements Map.Entry<K,V> {
        K key;
        V value;
        Entry<K,V> left;
        Entry<K,V> right;
        Entry<K,V> parent;
        boolean color = BLACK;
        /**
         * Make a new cell with given key, value, and parent, and with
         * {@code null} child links, and BLACK color.
         */
        Entry(K key, V value, Entry<K,V> parent) {
          this.key = key;
          this.value = value;
          this.parent = parent;
        }
        // ......
      }
      // ......
    }
    

    使用键值对方式存储数据的java.util.TreeMap容器,内部所有的键值对构成一棵红黑树。也就是说代表每一个键值对的TreeMap.Entry需要记录当前树结点的双亲结点(父结点)、左儿子结点和右儿子结点,以及当前树结点的颜色。所以,读者可以在以上代码片段中看到这些属性的定义,我们将在后续章节详细介绍TreeMap容器。

    2、Map结构中重要的接口和抽象类

    为了便于读者更深入理解java.util包中的TreeMap容器、HashMap容器和LinkedHashMap容器,我们将首先讲解几个重要的上层抽象类和接口:java.util.Map接口、java.util.SortedMap接口、java.util.NavigableMap接口和java.util.AbstractMap抽象类。

    2.1、java.util.Map接口

    java.util.Map接口是Java Collection Framework(JCF)框架中,Map体系的顶层接口定义。它定义了Map体系最基本的操作功能——针对K-V键值对这种操作结构的最基本操作功能。例如:

    • 建立一个指定值与指定键的关联,也就是建立一个新的K-V关联。如果操作之前已经存在这样的键-值关联,那么新的值将会替换调原有的值,并且之前的值将会被返回:
    V put(K key, V value);
    
    • 该方法用于清除当前map容器中所有键-值映射关系。
    void clear();
    
    • 返回一个指定键映射的指定值,如果当前map容器中不存在这个建,则返回null。
    V get(Object key);
    
    • 返回当前map容器中存储的K-V映射数量,如果映射数据大于Integer.MAX_VALUE (也就是231-1),那么就返回Integer.MAX_VALUE
    int size();
    
    • 这是一个判定方法,判定当前map容器中是否至少存在一个K-V映射数据。如果存在则返回true;其它情况返回false
    boolean isEmpty();
    

    以下java.util.Map接口的方法列表,摘自JDK 1.8版本:
    在这里插入图片描述

    2.2、java.util.SortedMap接口

    java.util.Map接口中定义的各种键值对读写方法,并不保证键的顺序。举个例子来说,K1、K2和K3三个键通过Map接口提供的put(K ,V)方法被放入了容器,当它们在容器中不一定按照存入的顺序进行存储。

    但很多业务场景下我们却需要存储在map容器中的这些键按照一定的规则进行有序存储,这时我们可能就需要使用实现了SortedMap接口的具体类了。需要注意的是:这里所说的有序存储不一定是线性存储的,例如使用红黑树结构进行的有序存储。SortedMap接口提供了很多和顺序存储有关的方法,例如:

    • 既然SortedMap接口下的实现类可以将键信息按照一定规则进行有序存储,那么其中自然就会用到Comparator比较器。通过以下方法可以当前容器使用的比较器。
    Comparator<? super K> comparator()
    
    • 既然容器中键信息是有序存储的,那么就可以指定开始的键信息以及结束位的键信息,并返回一个承载前两者之间的键值对引用信息的新的SortedMap容器。注意,由于新的SortedMap容器中存储的是这些键值对信息的引用,所以对新的SortedMap容器中键值对的写操作将会反应在当前容器中,反之亦然。另外,如果指定的开始位置的键信息和指定的结束位置的键信息相同,那么将返回一个空集合。
    SortedMap<K,V> subMap(K fromKey, K toKey)
    
    • 指定一个键信息,以下方法将返回一个新的SortedMap容器,后者存储的所有键值对的键信息都小于当前指定的键信息。除此之外,新的SortedMap容器的操作特性和subMap()方法返回的新容器的操作特性一致。需要注意的是,如果指定的键信息并不在当前SortedMap容器中,那么该方法将抛出IllegalArgumentException异常。
    SortedMap<K,V> headMap(K toKey);
    
    • 指定一个键信息,以下方法将返回一个新的SortedMap容器,后者存储的所有键值对的键信息都大于或者等于当前指定的键信息。初次之外,新的SortedMap容器的操作特性和headMap()方法返回的新容器的操作特性一致。
    SortedMap<K,V> tailMap(K fromKey);
    
    • 以下方法将返回当前SortedMap容器中,经过Comparator比较器比较后,值最小的那个键信息。
    K firstKey();
    
    • 以下方法将返回当前SortedMap容器中,经过Comparator比较器比较后,值最大的那个键信息。
    K lastKey();
    

    下图展示的java.util.SortedMap接口中完整的方法定义:

    在这里插入图片描述

    下图展示了java.util.Map接口、java.util.SortedMap接口和java.util.NavigableMap接口的继承关系:
    在这里插入图片描述

    2.3、java.util.NavigableMap接口

    如果说SortedMap接口为有序的键值对存储定义了基本操作,那么NavigableMap接口就是将和“有序”相关的操作进行细化。它精确定义了诸如返回上一个键/键值对、下一个键/键值对、最小键/键值对、最大键/键值对的一系列操作。

    • 以下两个方法返回一个键值映射信息/键信息,这个被返回的信息满足这样的条件:首先它属于一个一定小于当前给定键的集合,其次它是这个集合中键值最大的。如果容器中没有入参key所代表的键值映射信息/键信息,则返回null。
    Map.Entry<K,V> lowerEntry(K key);
    
    K lowerKey(K key);
    
    • 以下两个方法返回一个键值映射信息/键信息,这个被返回的信息满足这样的条件:首先它属于一个一定小于当前给定键的集合,其次它是这个集合中键值最大的。如果不存在这样的键值对映射信息/键信息,则返回入参key所代表的键值对映射信息/键信息本身;如果容器中没有入参key所代表的键值映射信息/键信息,则返回null。
    Map.Entry<K,V> floorEntry(K key);
    
    K floorKey(K key);
    
    • 以下两个方法返回一个键值映射信息/键信息,这个被返回的信息满足这样的条件:首先它属于一个一定大于当前给定键的集合,其次它是这个集合中键值最小的。如果容器中没有入参key所代表的键值映射信息/键信息,则返回null。
    Map.Entry<K,V> higherEntry(K key);
    
    K higherKey(K key);
    
    • 以下两个方法返回一个键值映射信息/键信息,这个被返回的信息满足这样的条件:首先它属于一个一定大于当前给定键的集合,其次它是这个集合中键值最小的。如果不存在这样的键值对映射信息/键信息,则返回入参key所代表的键值对映射信息/键信息本身;如果容器中没有入参key所代表的键值映射信息/键信息,则返回null。
    Map.Entry<K,V> ceilingEntry(K key);
    
    K ceilingKey(K key);
    

    下图展示的java.util.NavigableMap接口中完整的方法定义:

    在这里插入图片描述

    ============
    (接下文 源码阅读(16):Java中主要的Map结构——HashMap集合)

  • 相关阅读:
    VS2008编写MFC程序--使用opencv2.4()
    November 02nd, 2017 Week 44th Thursday
    November 01st, 2017 Week 44th Wednesday
    October 31st, 2017 Week 44th Tuesday
    October 30th, 2017 Week 44th Monday
    October 29th, 2017 Week 44th Sunday
    October 28th, 2017 Week 43rd Saturday
    October 27th, 2017 Week 43rd Friday
    October 26th, 2017 Week 43rd Thursday
    October 25th, 2017 Week 43rd Wednesday
  • 原文地址:https://www.cnblogs.com/liulaolaiu/p/11744200.html
Copyright © 2011-2022 走看看