类的描述
public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>
哈希表和链表实现的映射接口,具有可预测的迭代顺序。此实现与HashMap的不同之处在于,它维护一个贯穿其所有条目的双链接列表。此链表定义了迭代顺序,通常是key插入Map的顺序(插入顺序)。请注意,如果将键重新插入Map,则插入顺序不受影响。(如果调用m.put(k,v),而m.containsKey(k)将在调用之前立即返回true,则将密钥k重新插入到Map m中。)
此实现使其客户机免于HashMap(和Hashtable)提供的未指定、通常混乱的排序,而不会增加与TreeMap相关的成本。它可用于生成与原始Map顺序相同的Map,而不考虑原始Map的实现:
void foo(Map m) {
Map copy = new LinkedHashMap(m);
...
}
如果模块在输入时获取映射,复制它,然后返回由复制顺序决定的结果,则此技术特别有用。(客户通常喜欢按照相同的顺序归还物品。)
一个特殊的constructor提供创建链接的哈希映射的迭代是为了在该条目最后访问,从最近最少访问最近(存取顺序)。这种Map是非常适合于构建LRU缓存。调用put
,putIfAbsent
,get
,getOrDefault
,compute
,computeIfAbsent
,computeIfPresent
,或访问相应的条目merge
方法结果(假设它存在的调用完成后)。的replace
方法只能导致入口访问如果值代替。的putAll
方法在指定的Map生成每个映射一个入口进入,按关键值的映射是由指定的Map的进入提供了迭代器。没有其他方法生成入口访问。尤其在集合视图操作做不影响的支持Map迭代顺序。
removeEldestEntry(Map.Entry) 方法可能会被覆盖,以在新映射添加到映射时自动删除陈旧映射。
此类提供所有可选的 Map 操作,并允许空元素。与 HashMap 一样,它为基本操作(添加、包含和删除)提供恒定时间性能,假设哈希函数在桶中正确分散元素。由于维护链表的额外费用,性能可能略低于 HashMap,但有一个例外:对 LinkedHashMap 的集合视图进行迭代所需的时间与映射的大小成正比,而不管其容量如何.对 HashMap 的迭代可能更昂贵,需要与其容量成正比的时间。
链接的哈希映射有两个影响其性能的参数:初始容量和负载因子。它们的定义与 HashMap 一样。但是请注意,为此类选择过高的初始容量值的惩罚不如 HashMap 严重,因为此类的迭代时间不受容量的影响。
请注意,此实现不是同步的。如果多个线程并发访问链接的哈希映射,并且至少有一个线程在结构上修改映射,则必须在外部进行同步。这通常是通过同步一些自然封装地图的对象来完成的。如果不存在此类对象,则应使用 Collections.synchronizedMap 方法“包装”该地图。这最好在创建时完成,以防止对地图的意外不同步访问:
Map m = Collections.synchronizedMap(new LinkedHashMap(...));
结构修改是添加或删除一个或多个映射的任何操作,或者在访问顺序链接的散列映射的情况下,影响迭代顺序的任何操作。在插入顺序链接的哈希映射中,仅更改与映射中已包含的键关联的值不是结构修改。在按访问顺序链接的哈希映射中,仅使用 get 查询映射是一种结构修改。 )
该类的所有集合视图方法返回的集合的迭代器方法返回的迭代器都是fail-fast:如果在迭代器创建后的任何时间对映射进行结构修改,除了通过迭代器自己的remove方法以外的任何方式,迭代器将抛出 ConcurrentModificationException。因此,面对并发修改,迭代器快速而干净地失败,而不是冒着在未来不确定的时间出现任意、非确定性行为的风险。
请注意,无法保证迭代器的快速失败行为,因为一般而言,在存在非同步并发修改的情况下不可能做出任何硬保证。快速失败的迭代器会尽最大努力抛出 ConcurrentModificationException。因此,编写一个依赖此异常来确保其正确性的程序是错误的:迭代器的快速失败行为应该仅用于检测错误。
此类的所有集合视图方法返回的集合的 spliterator 方法返回的 spliterator 是后期绑定、fail-fast,并额外报告 Spliterator.ORDERED
静态、动态变量、内部类
//节点
static class Entry<K,V> extends HashMap.Node<K,V> {
Entry<K,V> before, after;
Entry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}
private static final long serialVersionUID = 3801124242820219131L;
/**
* 双向链表的头节点
*/
transient LinkedHashMap.Entry<K,V> head;
/**
* 双向链表的头节点的尾节点
*/
transient LinkedHashMap.Entry<K,V> tail;
/**
* 此链接哈希映射的迭代排序方法: true: 访问顺序, false: 迭代顺序
*/
final boolean accessOrder;
构造方法
public LinkedHashMap(int initialCapacity, float loadFactor) {
super(initialCapacity, loadFactor);
accessOrder = false;
}
public LinkedHashMap(int initialCapacity) {
super(initialCapacity);
accessOrder = false;
}
public LinkedHashMap() {
super();
accessOrder = false;
}
public LinkedHashMap(Map<? extends K, ? extends V> m) {
super();
accessOrder = false;
putMapEntries(m, false);
}
public LinkedHashMap(int initialCapacity,
float loadFactor,
boolean accessOrder) {
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
}
构造方法看, 其实是调用了父类的构造方法, 最后一个构造方法需要指定accessOrder,
所以accessOrder 默认是false ,插入顺序,指定为true的才是访问顺序
LinkHashMap其实存储元素的数据结构,其实是用了父类的数据结构,数组+红黑树+链表
然后在这个基础上加入了双向链表的概念
数据结构:
结构图从大牛哪里copy来了,地址:
Java中常见数据结构Map之LinkedHashMap_逆风的蔷薇-CSDN博客_linkedhashmap数据结构
常用方法
put
//父类的put
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) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
//LinkedHashMap重写了这个方法
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
//LinkedHashMap重写了这个方法
afterNodeInsertion(evict);
return null;
}
afterNodeAccess
void afterNodeAccess(Node<K,V> e) { // move node to last
LinkedHashMap.Entry<K,V> last;
//如果按访问顺序排序, 且tail != e ,将e节点移动到链表的最后面
//如果按插入顺序排序的话,这里是不用移动的,如果是e是tail节点的话,也是不用移动的
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;
}
}
afterNodeInsertion
void afterNodeInsertion(boolean evict) { // possibly remove eldest
LinkedHashMap.Entry<K,V> first;
//removeEldestEntry返回一直是false所以下面的都不会执行
if (evict && (first = head) != null && removeEldestEntry(first)) {
K key = first.key;
removeNode(hash(key), key, null, false, true);
}
}
afterNodeInsertion其实一直不执行,那在哪里将新添加的参数放到LinkHashMap的后面呢
//LinkHashMap 重写了HashMap的newNode方法,
//所以在putVal newNode的时候就已经操作了,将节点放到链表尾部
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;
}
TreeNode<K,V> newTreeNode(int hash, K key, V value, Node<K,V> next) {
TreeNode<K,V> p = new TreeNode<K,V>(hash, key, value, next);
linkNodeLast(p);
return p;
}
// link at the end of list
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;
}
}
get
public V get(Object key) {
Node<K,V> e;
if ((e = getNode(hash(key), key)) == null) //调用父类的HashMap的getNode
return null;
//如果是按访问顺序排序会执行afterNodeAccess
if (accessOrder)
afterNodeAccess(e);
return e.value;
}
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
if ((e = first.next) != null) {
if (first instanceof TreeNode)
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}
通过LinkedHashMap按访问顺序排序的特性,可以实现LRU
import java.util.*;
//扩展一下LinkedHashMap这个类,让他实现LRU算法
class LRULinkedHashMap<K,V> extends LinkedHashMap<K,V>{
//定义缓存的容量
private int capacity;
private static final long serialVersionUID = 1L;
//带参数的构造器
LRULinkedHashMap(int capacity){
//调用LinkedHashMap的构造器,传入以下参数
super(16,0.75f,true);
//传入指定的缓存最大容量
this.capacity=capacity;
}
//实现LRU的关键方法,如果map里面的元素个数大于了缓存最大容量,则删除链表的顶端元素
@Override
public boolean removeEldestEntry(Map.Entry<K, V> eldest){
System.out.println(eldest.getKey() + "=" + eldest.getValue());
return size()>capacity;
}
}
//测试类
class Test{
public static void main(String[] args) throws Exception{
//指定缓存最大容量为4
Map<Integer,Integer> map=new LRULinkedHashMap<>(4);
map.put(9,3);
map.put(7,4);
map.put(5,9);
map.put(3,4);
map.put(6,6);
//总共put了5个元素,超过了指定的缓存最大容量
//遍历结果
for(Iterator<Map.Entry<Integer,Integer>> it=map.entrySet().iterator();it.hasNext();){
System.out.println(it.next().getKey());
}
}
}