1. 定义特点
LinkedHashMap结合了hashmap和双向链表
LinkedHashMap是HashMap的子类,有着和HashMap的多数特性。
其特点大概有:
- key和value都允许为空
- key可重复可覆盖,value可重复
- 有序的
- 非线程安全的
还可实现LRU (最近最少使用)算法
通过查看源码 也可看到其这个类的大概意思:
-
实现哈希表和链表的Map接口,具有可预测的迭代顺序。
-
它维护一个贯穿其所有条目的双链接列表。 这个链表定义了迭代顺序,这通常是键插入到映射中的顺序(插入顺序)。
-
这个类提供了所有可选的Map操作,并允许空元素。 与HashMap一样,它为基本操作(添加、包含和删除)提供了常量时间性能,前提是哈希函数将元素正确地分散在存储桶中。 由于维护链表的额外开销,性能可能略低于HashMap,但有一个例外:对LinkedHashMap的集合视图进行迭代所需的时间与map的大小成比例,而与map的容量无关。 在HashMap上迭代可能更昂贵,需要的时间与其容量成正比。
-
实现不是同步的。 如果多个线程并发地访问一个链接的哈希映射,并且至少有一个线程在结构上修改了这个映射,那么它必须在外部进行同步。 这通常是通过对自然封装了映射的对象进行同步来实现的。 如果不存在这样的对象,映射应该使用集合进行“包装”。 synchronizedMap方法。 这最好在创建时完成,以防止意外的不同步访问地图:
Map m =集合。 synchronizedMap(新LinkedHashMap(…)); -
结构修改是添加或删除一个或多个映射的任何操作,或者在访问顺序链接哈希映射的情况下,影响迭代顺序。 在插入顺序的链接哈希映射中,仅仅更改与已经包含在映射中的键相关联的值并不是结构修改。 在访问顺序链接哈希映射中,仅仅使用get查询映射就是对结构的修改。)
以下源码主要是jdk1.8以上的
2. 源码
类结构的具体定义:
public class LinkedHashMap<K,V>
extends HashMap<K,V>
implements Map<K,V>
多了双链表的定义
也就是双向链表的头尾节点以及访问的迭代顺序(布尔值表示正序与反序)
- header是LinkedHashMap所维护的双向链表的头结点
- tail是尾节点
- accessOrder用于决定具体的迭代顺序
尾结点的后继其实也指向了header,所以就是双向循环链表
/**
* The head (eldest) of the doubly linked list.
*/
transient LinkedHashMap.Entry<K,V> head;
/**
* The tail (youngest) of the doubly linked list.
*/
transient LinkedHashMap.Entry<K,V> tail;
/**
* The iteration ordering method for this linked hash map: {@code true}
* for access-order, {@code false} for insertion-order.
*
* @serial
*/
final boolean accessOrder;
五个构造函数:
- 四个构造方法都将accessOrder设为false,默认是插入顺序排序的
- 第五个构造方法自定义传入的accessOrder的值,指定双向循环链表中排序规则
/*用指定的初始容量和负载因子构造一个空的按插入顺序排列的LinkedHashMap实例。
参数:
initialCapacity—初始容量
loadFactor—负载因子
抛出:
IllegalArgumentException -如果初始容量是负的或者负载系数是非正的*/
public LinkedHashMap(int initialCapacity, float loadFactor) {
super(initialCapacity, loadFactor);
accessOrder = false;
}
/*用指定的初始容量和默认负载因子(0.75)构造一个空的按插入顺序排列的LinkedHashMap实例。
参数:
initialCapacity—初始容量
抛出:
IllegalArgumentException -如果初始容量为负数 */
public LinkedHashMap(int initialCapacity) {
super(initialCapacity);
accessOrder = false;
}
/*Constructs an empty insertion-ordered LinkedHashMap instance with the default initial capacity (16) and load factor (0.75).*/
public LinkedHashMap() {
super();
accessOrder = false;
}
/*用与指定映射相同的映射构造一个按插入顺序排列的LinkedHashMap实例。 LinkedHashMap实例是用一个默认的负载因子(0.75)和一个初始容量创建的,该容量足以容纳指定映射中的映射。
参数:
M -该地图的映射将被放置在该地图中
抛出:
NullPointerException -如果指定的映射为空 */
public LinkedHashMap(Map<? extends K, ? extends V> m) {
super();
accessOrder = false;
putMapEntries(m, false);
}
/*用指定的初始容量、负载因子和排序模式构造一个空的LinkedHashMap实例。
参数:
initialCapacity—初始容量
loadFactor—负载因子
accessOrder -排序模式-访问顺序为true,插入顺序为false
抛出:
IllegalArgumentException -如果初始容量是负的或者负载系数是非正的 */
public LinkedHashMap(int initialCapacity,
float loadFactor,
boolean accessOrder) {
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
}
其基本结构entry
before、after是用于维护Entry插入的先后顺序的.
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);
}
}
维护链表的操作:
删除,插入,获取节点之后,对链表进行维护
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;
}
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);
}
}
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;
}
}
get操作 具体和hashmap的get操作大致意思
返回指定键映射到的值,如果该映射不包含键的映射,则为空。
更正式地说,如果这个映射包含一个从键k到值v的映射,这样(key==null ? K ==null: key.equals(K)),然后这个方法返回v; 否则返回null。 (这样的映射最多只能有一个。)
返回值为null并不一定表明映射没有包含键的映射; 也有可能映射显式地将键映射为空。 可以使用containsKey操作来区分这两种情况。
public V get(Object key) {
Node<K,V> e;
if ((e = getNode(key)) == null)
return null;
if (accessOrder)
afterNodeAccess(e);
return e.value;
}
其他的put remove操作大致相通,都是在双链表的基础上操作
对此可以配合lru算法进行结合
关于lru,面试题可是高频题
3. 与LRU(最近最少使用算法)结合
用LinkedHashMap实现LRU算法时,构造方法需要将accessOrder的值设置为true
这部分代码来源于leetcode
链接如下:
lru缓存机制
class LRUCache extends LinkedHashMap<Integer, Integer>{
private int capacity;
public LRUCache(int capacity) {
super(capacity, 0.75F, true);
this.capacity = capacity;
}
public int get(int key) {
return super.getOrDefault(key, -1);
}
public void put(int key, int value) {
super.put(key, value);
}
@Override
protected boolean removeEldestEntry(Map.Entry<Integer, Integer> eldest) {
return size() > capacity;
}
}