[JDK1.8] JAVA集合 LinkedHashMap源码浅析
文章目录
- 一 简述
- 二 架构图
- 三 构造方法
- LinkedHashMap()
- LinkedHashMap(Map)
- LinkedHashMap(int, float, boolean)
- LinkedHashMap(int)
- LinkedHashMap(int, float)
- 四 核心方法
- 添加 newNode(int, K, V, Node)
- linkNodeLast(Node)
- afterNodeInsertion
- 获取 get
- 访问排序 afterNodeAccess
- 移除 afterNodeRemoval
- afterNodeRemoval
- 移除最旧的节点 removeEldestEntry
- 迭代器 Iterator
源码和注释来自JDK api 文档
一 简述
Map 接口的哈希表和链接列表实现,具有可预知的迭代顺序。此实现与 HashMap 的不同之处在于,后者维护着一个运行于所有条目的双重链接列表。此链接列表定义了迭代顺序,该迭代顺序通常就是将键插入到映射中的顺序(插入顺序)。注意,如果在映射中重新插入 键,则插入顺序不受影响。(如果在调用 m.put(k, v) 前 m.containsKey(k) 返回了 true,则调用时会将键 k 重新插入到映射 m 中。)
此实现可以让客户避免未指定的、由 HashMap(及 Hashtable)所提供的通常为杂乱无章的排序工作,同时无需增加与 TreeMap 相关的成本。使用它可以生成一个与原来顺序相同的映射副本,而与原映射的实现无关:
void foo(Map m) {
Map copy = new LinkedHashMap(m);
...
}
如果模块通过输入得到一个映射,复制这个映射,然后返回由此副本确定其顺序的结果,这种情况下这项技术特别有用。(客户通常期望返回的内容与其出现的顺序相同。)
提供特殊的构造方法来创建链接哈希映射,该哈希映射的迭代顺序就是最后访问其条目的顺序,从近期访问最少到近期访问最多的顺序(访问顺序)。这种映射很适合构建 LRU 缓存。调用 put 或 get 方法将会访问相应的条目(假定调用完成后它还存在)。putAll 方法以指定映射的条目集迭代器提供的键-值映射关系的顺序,为指定映射的每个映射关系生成一个条目访问。任何其他方法均不生成条目访问。特别是,collection 视图上的操作不 影响底层映射的迭代顺序。
可以重写 removeEldestEntry(Map.Entry) 方法来实施策略,以便在将新映射关系添加到映射时自动移除旧的映射关系。
此类提供所有可选的 Map 操作,并且允许 null 元素。与 HashMap 一样,它可以为基本操作(add、contains 和 remove)提供稳定的性能,假定哈希函数将元素正确分布到桶中。由于增加了维护链接列表的开支,其性能很可能比 HashMap 稍逊一筹,不过这一点例外:LinkedHashMap 的 collection 视图迭代所需时间与映射的大小 成比例。HashMap 迭代时间很可能开支较大,因为它所需要的时间与其容量 成比例。
链接的哈希映射具有两个影响其性能的参数:初始容量和加载因子。它们的定义与 HashMap 极其相似。要注意,为初始容量选择非常高的值对此类的影响比对 HashMap 要小,因为此类的迭代时间不受容量的影响。
注意,此实现不是同步的。如果多个线程同时访问链接的哈希映射,而其中至少一个线程从结构上修改了该映射,则它必须 保持外部同步。这一般通过对自然封装该映射的对象进行同步操作来完成。如果不存在这样的对象,则应该使用 Collections.synchronizedMap 方法来“包装”该映射。最好在创建时完成这一操作,以防止对映射的意外的非同步访问:
Map m = Collections.synchronizedMap(new LinkedHashMap(...));
结构修改是指添加或删除一个或多个映射关系,或者在按访问顺序链接的哈希映射中影响迭代顺序的任何操作。在按插入顺序链接的哈希映射中,仅更改与映射中已包含键关联的值不是结构修改。在按访问顺序链接的哈希映射中,仅利用 get 查询映射不是结构修改。)
Collection(由此类的所有 collection 视图方法所返回)的 iterator 方法返回的迭代器都是快速失败 的:在迭代器创建之后,如果从结构上对映射进行修改,除非通过迭代器自身的 remove 方法,其他任何时间任何方式的修改,迭代器都将抛出 ConcurrentModificationException。因此,面对并发的修改,迭代器很快就会完全失败,而不冒将来不确定的时间任意发生不确定行为的风险。
注意,迭代器的快速失败行为无法得到保证,因为一般来说,不可能对是否出现不同步并发修改做出任何硬性保证。快速失败迭代器会尽最大努力抛出 ConcurrentModificationException。因此,编写依赖于此异常的程序的方式是错误的,正确做法是:迭代器的快速失败行为应该仅用于检测程序错误。
二 架构图
LinkedHashMap: 继承了HashMap
主要字段
名称 | 注释 |
---|---|
head | 双向链表的头(最大)。 |
tail | 双向链表的尾部(最小)。 |
accessOrder | 此链接哈希映射的迭代排序方法:对于访问顺序为true,对于插入顺序为false。 |
LinkedHashMap.Entry 继承了 Node
主要字段
名称 | 注释 |
---|---|
before | 前一个节点 |
after | 后一个节点 |
三 构造方法
LinkedHashMap()
注释: 使用默认初始容量(16)和加载因子(0.75)构造一个空的插入顺序LinkedHashMap实例。
public LinkedHashMap() {
super();
accessOrder = false;
}
LinkedHashMap(Map)
注释:使用与指定映射相同的映射构造一个插入有序的LinkedHashMap实例。 LinkedHashMap实例使用默认加载因子(0.75)和足以保存指定映射中的映射的初始容量创建。
public LinkedHashMap(Map<? extends K, ? extends V> m) {
super();
accessOrder = false;
putMapEntries(m, false);
}
LinkedHashMap(int, float, boolean)
注释:使用指定的初始容量,加载因子和排序模式构造一个空的LinkedHashMap实例。
public LinkedHashMap(int initialCapacity,
float loadFactor,
boolean accessOrder) {
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
}
LinkedHashMap(int)
注释:使用指定的初始容量和默认加载因子(0.75)构造一个空的插入排序的LinkedHashMap实例。
public LinkedHashMap(int initialCapacity) {
super(initialCapacity);
accessOrder = false;
}
LinkedHashMap(int, float)
注释:使用指定的初始容量和加载因子构造一个空的插入排序的LinkedHashMap实例。
public LinkedHashMap(int initialCapacity, float loadFactor) {
super(initialCapacity, loadFactor);
accessOrder = false;
}
四 核心方法
添加 newNode(int, K, V, Node)
注释:LinkedHashMap 调用 HashMap 的put方法,重写了 newNode方法,使得添加的元素安装双链表的形式记录
// 这个方法在 HashMap中
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
··· 省略
tab[i] = newNode(hash, key, value, null);
··· 省略
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
}
Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
LinkedHashMap.Entry<K,V> p =
new LinkedHashMap.Entry<K,V>(hash, key, value, e);
linkNodeLast(p);
return p;
}
linkNodeLast(Node)
注释:链接在列表的末尾
private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
LinkedHashMap.Entry<K,V> last = tail;
tail = p;
if (last == null)
head = p;
else {
p.before = last;
last.after = p;
}
}
afterNodeInsertion
注释:这个方法实现移除掉最近最少使用的节点,需要重写 removeEldestEntry
void afterNodeInsertion(boolean evict) { // possibly remove eldest
LinkedHashMap.Entry<K,V> first;
if (evict && (first = head) != null && removeEldestEntry(first)) {
K key = first.key;
removeNode(hash(key), key, null, false, true);
}
}
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
return false;
}
获取 get
注释:
public V get(Object key) {
Node<K,V> e;
if ((e = getNode(hash(key), key)) == null)
return null;
if (accessOrder)
afterNodeAccess(e);
return e.value;
}
访问排序 afterNodeAccess
注释: 这个方法是在创建LinkedHashMap时 accessOrder 设置为 true;调用这个方法将把访问的这个节点移动到链表的尾部,LRU算法,LRU(least recently used)最近最少使用。
void afterNodeAccess(Node<K,V> e) { // move node to last
LinkedHashMap.Entry<K,V> last;
if (accessOrder && (last = tail) != e) {
LinkedHashMap.Entry<K,V> p =
(LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
p.after = null;
if (b == null)
head = a;
else
b.after = a;
if (a != null)
a.before = b;
else
last = b;
if (last == null)
head = p;
else {
p.before = last;
last.after = p;
}
tail = p;
++modCount;
}
}
移除 afterNodeRemoval
注释:这个方法在HashMap中,删除后调用 afterNodeRemoval
public V remove(Object key) {
Node<K,V> e;
return (e = removeNode(hash(key), key, null, false, true)) == null ?
null : e.value;
}
final Node<K,V> removeNode(int hash, Object key, Object value,
boolean matchValue, boolean movable) {
··· 省略
if (node != null && (!matchValue || (v = node.value) == value ||
(value != null && value.equals(v)))) {
if (node instanceof TreeNode)
((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
else if (node == p)
tab[index] = node.next;
else
p.next = node.next;
++modCount;
--size;
afterNodeRemoval(node);
return node;
}
··· 省略
}
afterNodeRemoval
注释:LinkedHashMap 重写了这个方法,断开链表的指定节点
void afterNodeRemoval(Node<K,V> e) { // unlink
LinkedHashMap.Entry<K,V> p =
(LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
p.before = p.after = null;
if (b == null)
head = a;
else
b.after = a;
if (a == null)
tail = b;
else
a.before = b;
}
移除最旧的节点 removeEldestEntry
源码注释
如果此映射应删除其最旧条目,则返回true。在将新条目插入地图后,put和putAll将调用此方法。它为实现者提供了在每次添加新条目时删除最旧条目的机会。如果映射表示缓存,这将非常有用:它允许映射通过删除过时条目来减少内存消耗。
示例使用:此覆盖将允许地图增长到100个条目,然后在每次添加新条目时删除最旧的条目,保持100个条目的稳定状态。
private static final int MAX_ENTRIES = 100;
protected boolean removeEldestEntry(Map.Entry eldest){
return size()> MAX_ENTRIES;
}
此方法通常不会以任何方式修改映射,而是允许映射根据其返回值进行自我修改。此方法允许直接修改映射,但如果它这样做,则必须返回false(表示映射不应尝试进一步修改)。从此方法中修改映射后返回true的效果未指定。
此实现仅返回false(因此此映射的行为类似于普通映射 - 永远不会删除最旧的元素)。
把LinkedHashMap 的方法重写了,节点数量达到一定程度返回true就可以移除最旧的节点,
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
return false;
}
实例:
当容器的数量大于1024时,添加就会把head的节点移除掉
Map<String, String> linkedHashMap = new LinkedHashMap<String, String>(16, .75f, true) {
private static final long serialVersionUID = -3295770436308811037L;
private static final int SIZE_MAX = 1024;
@Override
protected boolean removeEldestEntry(java.util.Map.Entry<String, String> eldest) {
return size() > SIZE_MAX;
}
};
for (int i = 0; i < 1200; i++) {
linkedHashMap.put("" + i, "a_" + i);
}
System.out.println(linkedHashMap);
迭代器 Iterator
架构图:
LinkedHashIterator
// Iterators
abstract class LinkedHashIterator {
LinkedHashMap.Entry<K,V> next; // 下一个节点的指针
LinkedHashMap.Entry<K,V> current; // 当前节点的指针
int expectedModCount;
// 构造方法, 初始化next节点
LinkedHashIterator() {
next = head;
expectedModCount = modCount;
current = null;
}
// 是否有下一个节点
public final boolean hasNext() {
return next != null;
}
// 获取下一个节点
final LinkedHashMap.Entry<K,V> nextNode() {
LinkedHashMap.Entry<K,V> e = next;
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
if (e == null)
throw new NoSuchElementException();
current = e;
next = e.after;
return e;
}
// 移除最近一次调用nextNode 返回的节点
public final void remove() {
Node<K,V> p = current;
if (p == null)
throw new IllegalStateException();
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
current = null;
K key = p.key;
removeNode(hash(key), key, null, false, false);
expectedModCount = modCount;
}
}
LinkedKeyIterator
LinkedValueIterator
LinkedEntryIterator
final class LinkedKeyIterator extends LinkedHashIterator
implements Iterator<K> {
public final K next() { return nextNode().getKey(); }
}
final class LinkedValueIterator extends LinkedHashIterator
implements Iterator<V> {
public final V next() { return nextNode().value; }
}
final class LinkedEntryIterator extends LinkedHashIterator
implements Iterator<Map.Entry<K,V>> {
public final Map.Entry<K,V> next() { return nextNode(); }
}