• 源码阅读(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集合)

  • 相关阅读:
    if elseif else
    java编程思想第四版中net.mindview.util包
    eclipse git插件配置
    php面试常用算法
    数据库字段类型中char和Varchar区别
    MySQL的数据库引擎的类型及区别
    windows系统中eclipse C c++开发环境的搭建
    launch failed.Binary not found in Linux/Ubuntu解决方案
    技术团队的情绪与效率
    如何有效使用Project(2)——进度计划的执行与监控
  • 原文地址:https://www.cnblogs.com/liulaolaiu/p/11744200.html
Copyright © 2020-2023  润新知