注:该系列文章总结自JDK1.9源码的文档注释和源码,我对源码的部分中文注释已经同步到Github:https://github.com/Dodozhou/JDK
Map的实现体系中,常用的类和接口可以大致分为三层。下面一一说明这些类或接口的特点和作用。
第一层包含一个抽象类AbstractMap和一个接口SortedMap。AbstractMap简单实现了Map接口的大部分操作,降低了实现Map接口的难度,第二层的所有类均继承了该类,它是Map接口的框架性形式。SortedMap接口定义了有序Map需要遵循的规则和实现的方法,有序Map都需要实现该接口。
该类提供了一个Map接口的框架性实现,降低了实现Map接口的难度。该Map实现允许存入null值。它实现了Map接口的绝大多数方法,如get(Object):
/**
* {
@inheritDoc}
*
* 通过entrySet().iterator()对map进行遍历,如果key等于null,则找出ey为
* null的映射,并返回它的value。如果key不等于null,则找到匹配的映射,并返回value。
* 如果找不到匹配的key,则返回null。
* 该方法花费线性时间,很多实现会重写该方法。
* 由于该实现允许null值的插入,因此不可以仅仅通过返回值来判断key是否存在。这在Map接口中已经说过,
* 可以结合containsKey方法进行判断。
* @implSpec
* This implementation iterates over {
@code entrySet()} searching
* for an entry with the specified key. If such an entry is found,
* the entry's value is returned. If the iteration terminates without
* finding such an entry, {
@code null} is returned. Note that this
* implementation requires linear time in the size of the map; many
* implementations will override this method.
*
* @throws ClassCastException {
@inheritDoc}
* @throws NullPointerException {
@inheritDoc}
*/
public V get(Object key) {
Iterator> i = entrySet().iterator();
if (key==null) {
while (i.hasNext()) {
Entry e = i.next();
if (e.getKey()==null)
return e.getValue();
}
} else {
while (i.hasNext()) {
Entry e = i.next();
if (key.equals(e.getKey()))
return e.getValue();
}
}
return null;
}
同map接口一样,继承该类需要至少实现两个构造函数,一个无参,一个以map为参数。
此外,该类还重写了继承自Object的equals(Object)、HashCode()、toString()、Clone()方法,使得这些方法适用于Map类型。以equals为例:
public boolean equals(Object o) {
if (o == this) //引用判断
return true;
if (!(o instanceof Map)) //类型判断
return false;
Map,?> m = (Map,?>) o;
if (m.size() != size()) //大小判断
return false;
try {
for (Entry e : entrySet()) { //对所有entry进行循环遍历
K key = e.getKey();
V value = e.getValue();
if (value == null) {
if (!(m.get(key) == null && m.containsKey(key))) //对键判断
return false;
} else {
if (!value.equals(m.get(key))) //对值判断
return false;
}
}
} catch (ClassCastException unused) {
return false;
} catch (NullPointerException unused) {
return false;
}
return true;
}
该接口继承了Map接口,它定义了key有序的Map。key如果实现Comparable接口,那么可以使用自然顺序进行排序。否则,在创建该map是提供外部comparator。对该map返回的视图(entrySet, keySet and values 方法)进行迭代遍历时就是根据key的顺序进行。该类还额外提供了几种方法来利用key有序这个特性。
该接口要求所有的key都必须是可以进行比较的,要么通过k1.compareTo(k2),要么通过comparator.compare(k1, k2)进行比较。它同时还规定了所有通用的sorted map需要提供4个构造器,分别是:
接口提供了几个根据key的范围返回submaps的方法。这些范围都是半闭半开的。可以通过m.subMap(low, high+”\0”)来获得全闭范围。或者通过m.subMap(low+”\0”, high)来获得全开范围。
该接口定义了几种利用key有序特性的方法:
如最开始的结构图所示,所有的类都实现了AbstractMap,只有TreeMap实现了SortedMap。接下来对这些类的作用和原理进行一个简略的说明。
HashMap是一个基于哈希表来存储键值对,以期获得O(1)的操作时间。它的底层实现时这样的:
他有如下特点:
WeakHashMap的key只保留对实际对象的弱引用,这意味着如果WeakHashMap对象的key所引用的对象没有被其他强引用变量所引用,则这些key所引用的对象可能被垃圾回收,WeakHashMap也可能自动删除这些key所对应的key-value对。
在IdentityHashMap中,当且仅当两个key严格相等(key1==key2)时,IdentityHashMap才认为两个key相等;相对于普通HashMap而言,只要key1和key2通过equals()方法返回true,且它们的hashCode值相等即可。
更具体的信息参加博客:https://blog.csdn.net/zhoucheng05_13/article/details/79834024
可以将HashTable理解为HashMap的线程安全版本,但是它们在哈希算法等方面还是存在一些不同。该列已经逐渐别废弃,在线程安全的场景下,JDK推荐使用HashMap代替Hashtable。如果需要一个线程安全的高并发实现,那么建议使用ComcurrentHashMap而不是该类。
HashTable具有如下特点:
TreeMap是一个基于NavigableMap的红黑树实现。该实现保证了containsKey、get、put、remove操作的log(n)时间界。
TreeMap最重要的特点是,使用了红黑树这种有序平衡二叉树来保持了key的顺序,同时又利用有序二叉树的常用操作的log(n)时间界。
注意,该方法不是同步的。如果要在多线程环境下使用,那么需要进行外部同步,通常是通过锁定一个外部对象,如果不存在这样的对象,那么应该使用Collections.synchronizedSortedMap进行包裹。
由该类集合视图方法返回的集合,它们的迭代器都是快速失败机制的。(快速失败机制有什么用?首先,快速失败是通过在每次进行操作前判断modCount是否等于expectedModCount,如果不等则说明集合在外部被修改了,那么抛出异常,终止遍历,将风险降到最小,而不是在未来的某一不确定的时间,冒任意不确定的风险。该机制并不可靠(为什么不可靠?存在不同步的并发修改时,不可能做出任何绝对的保证),不能依赖于该机制编程,官方文档中指明,该机制仅用于检测错误)
关于红黑树,我还不了解,因此这里就不多说了。
第三层是HashMap的子类,LinkedHashMap。
LinkedHashMap是通过链表的方式使得插入变得有序的Map接口实现。这个实现是为了解决HashMap和HashTable无序问题,而又不增加像TreeMap那样的成本。它基于HashMap,并在HashMap的基础上为每个节点增加了一个双向链,形成了一个双向链表,以此来保证了元素的有序。
**重点!**LinkedHashMap的一个特殊的构造器LinkedHashMap(int,float,boolean)被用来创建一个从最近最少到最近被访问的访问顺序排序的LinkedHashMap。这样的map非常适合用于实现LRU缓存。关于LinkedHashMap与LRU的关系,参见:https://blog.csdn.net/zhoucheng05_13/article/details/79832885
LinkedHashMap与HashMap的不同点有: