(接上文《源码阅读(14):Java中主要的Queue、Deque结构——PriorityQueue集合(下)》)
为什么要先介绍Java中的主要Map结构呢?如果读者是从本专题第一篇文章开始阅读的话,那么就应该清楚目前我们整个专题还在介绍Java中java.util.Collection接口的注意要实现集合 ,具体来说就应该是List集合、Queue/Deque集合以及Set集合。那么List集合、Queue/Deque集合介绍完后,理所当然就应该开始介绍Set集合。下图为Java中重要的Set集合的构建体系:
是的,从最直观的讲解思路考虑,整个专题是应该按照这样的思路进行介绍。但事实上Java中重要的Set集合内部实现全部基于对应的Map
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 HashMap 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集合实现。
首先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容器尤为重要。
上文已经提到,Map容器中存储的是键值对,既是一个键信息和一个值信息的映射关系。Map容器中可以有成千上万的键值对信息,每一个键值对都使用Map.Entry
Map.Entry
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容器。
为了便于读者更深入理解java.util包中的TreeMap容器、HashMap容器和LinkedHashMap容器,我们将首先讲解几个重要的上层抽象类和接口:java.util.Map接口、java.util.SortedMap接口、java.util.NavigableMap接口和java.util.AbstractMap抽象类。
java.util.Map接口是Java Collection Framework(JCF)框架中,Map体系的顶层接口定义。它定义了Map体系最基本的操作功能——针对K-V键值对这种操作结构的最基本操作功能。例如:
V put(K key, V value);
void clear();
V get(Object key);
int size();
boolean isEmpty();
以下java.util.Map接口的方法列表,摘自JDK 1.8版本:
java.util.Map接口中定义的各种键值对读写方法,并不保证键的顺序。举个例子来说,K1、K2和K3三个键通过Map接口提供的put(K ,V)方法被放入了容器,当它们在容器中不一定按照存入的顺序进行存储。
但很多业务场景下我们却需要存储在map容器中的这些键按照一定的规则进行有序存储,这时我们可能就需要使用实现了SortedMap接口的具体类了。需要注意的是:这里所说的有序存储不一定是线性存储的,例如使用红黑树结构进行的有序存储。SortedMap接口提供了很多和顺序存储有关的方法,例如:
Comparator<? super K> comparator()
SortedMap<K,V> subMap(K fromKey, K toKey)
SortedMap<K,V> headMap(K toKey);
SortedMap<K,V> tailMap(K fromKey);
K firstKey();
K lastKey();
下图展示的java.util.SortedMap接口中完整的方法定义:
下图展示了java.util.Map接口、java.util.SortedMap接口和java.util.NavigableMap接口的继承关系:
如果说SortedMap接口为有序的键值对存储定义了基本操作,那么NavigableMap接口就是将和“有序”相关的操作进行细化。它精确定义了诸如返回上一个键/键值对、下一个键/键值对、最小键/键值对、最大键/键值对的一系列操作。
Map.Entry<K,V> lowerEntry(K key);
K lowerKey(K key);
Map.Entry<K,V> floorEntry(K key);
K floorKey(K key);
Map.Entry<K,V> higherEntry(K key);
K higherKey(K key);
Map.Entry<K,V> ceilingEntry(K key);
K ceilingKey(K key);
下图展示的java.util.NavigableMap接口中完整的方法定义:
============
(接下文 源码阅读(16):Java中主要的Map结构——HashMap集合)