目录
序言:
1:LinkedHashMap中的put(key,value)
1.1:LinkedHashMap中对putVal()方法源码描述:
1.2:LinkedHashMap中对节点操作方法源码描述:
1.3:LinkedHashMap中afterNodeAcces()方法源码描述:
1.4:LinkedHashMap中afterNodeInsertion()方法源码描述:
1.5:LinkedHashMap中afterNodeRemoval()方法描述:
2:LinkedHashMap中get(key)源码描述:
3:LinkedHashMap中迭代源码描述
4:总结:
5:Demo代码
6:应用
LinkedHashMap从类名中可以看出此类与链表于HashMap,如下图所示
从上述源码可以看出LinkedHashMap继承于HashMap,也就是说HashMap拥有的能力LinkedHashMap也同时拥有,与HashMap不同在于LinkedHashMap中维护了一个双向链表(此链表会中包含了所有数据),并且重写Node对象,并通过记录双向链表的始终(head,tail)节点用于保证此类可以做到顺序读写(并不仅仅如此)。
与之前对HashMap描述的一样,我们从put(key,value),到get(key),开始一步步查看数据在LinkedHashMap中的运行过程。查看源码后发现在LinkedHashMap中并没有重写put(key,value)方法,还是使用了HashMap中put方法,所以对于数据写入的整体流程和HashMap没什么大的区别,不同在于LinkedHashMap对所有对于数据节点的操作(newNode,replacementNode,newTreeNode,replacementTreeNode)都进行了重写。我们知道在HashMap的数据结构,所有数据会根据key将数据分布在不同位桶中,每个位桶以链表(若超过8为改变为红黑树)的结构存在,所以在HashMap从数据层面上来看局部是联系一起,整体却是离散的。LinkedHashMap在数据存储的结构大体与HashMap相同,不同在于重写的这些节点方法会将所有数据紧密的联系在一起(每个节点都会按照写入顺序记录上一个节点),所以LinkedHashMap在数据层面上来看局部是离散的,整体上是联系在一起的。整体的写入过程可以如下图所示:
对照着HashMap中putVal()讲解:
/**
*重写HashMap中newNode方法
*/
Node newNode(int hash, K key, V value, Node e) {
//创建新的节点
LinkedHashMap.Entry p =
new LinkedHashMap.Entry(hash, key, value, e);
linkNodeLast(p);
return p;
}
/**
* 将当前节点追加到双向链表 写入过程的时间复杂度为O(1)
*/
private void linkNodeLast(LinkedHashMap.Entry p) {
LinkedHashMap.Entry last = tail;
tail = p;
//判断双向链表中尾是否为null
if (last == null)
//说明此链表为null 将当前节点置为链表头
head = p;
else {
//说明已存在链表 将当前的节点加入链表中
p.before = last;
last.after = p;
}
}
/**
*该方法的作用将双向链表中某节点替换为其它节点 替换的时间复杂度为O(1)
*/
private void transferLinks(LinkedHashMap.Entry src,
LinkedHashMap.Entry dst) {
LinkedHashMap.Entry b = dst.before = src.before;
LinkedHashMap.Entry a = dst.after = src.after;
if (b == null)
head = dst;
else
b.after = dst;
if (a == null)
tail = dst;
else
a.before = dst;
}
/**
* 替换节点
*/
Node replacementNode(Node p, Node next) {
LinkedHashMap.Entry q = (LinkedHashMap.Entry)p;
LinkedHashMap.Entry t =
new LinkedHashMap.Entry(q.hash, q.key, q.value, next);
transferLinks(q, t);
return t;
}
/**
* 创建一个红黑树节点(当位桶中元素超过8个转变为红黑树之后)
*/
TreeNode newTreeNode(int hash, K key, V value, Node next) {
TreeNode p = new TreeNode(hash, key, value, next);
//将创建树节点加入双向链表中
linkNodeLast(p);
return p;
}
/**
* 替换树节点
*/
TreeNode replacementTreeNode(Node p, Node next) {
LinkedHashMap.Entry q = (LinkedHashMap.Entry)p;
TreeNode t = new TreeNode(q.hash, q.key, q.value, next);
transferLinks(q, t);
return t;
}
//此方法作用每次访问某节点被访问之后将当前节点移动到双向链中最后一位
void afterNodeAccess(Node e) { // move node to last
LinkedHashMap.Entry last;
/**
* accessOrder作用:查询迭代时的访问顺序
* 值为true时,表示按照访问顺序迭代;如果key被操作过,那么此key对应的节点就会被移动到双向链表的head端基于此特性可以作为LRU算法的Java实现 后续详细介绍
* 值为false时,表示按照插入顺序迭代
* 该参数LinkedHashMap初始化的时候指定 不指定默认为false
*
*/
if (accessOrder && (last = tail) != e) {
LinkedHashMap.Entry p =
(LinkedHashMap.Entry)e, b = p.before, a = p.after;
/**
*以下的各种交换可以如下图所示
* 就是将当前节点e从双向链中移动到最后一个位置,如果当前节点已经是最后了不做任何处理
*/
//将当前节点下一个节点置null
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;
}
}
//当有新的节点插入之后需要进行的动作
//参数evict 是否开启驱逐操作,即当一个新的元素插入之后是否需要移除一个元素
void afterNodeInsertion(boolean evict) { // possibly remove eldest
LinkedHashMap.Entry first;
//需要满足开启驱逐策略并且当前链表中不为空removeEldestEntry定义哪些情况下需要进行移除操作
if (evict && (first = head) != null && removeEldestEntry(first)) {
K key = first.key;
//移除头节点 如果按照开启访问顺序来排序的化,尾节点其实是最新被访问到的节点
removeNode(hash(key), key, null, false, true);
}
}
/**
*由使用者自定义的策略 默认为false
*/
protected boolean removeEldestEntry(Map.Entry eldest) {
return false;
}
/**
*该方法将双向链表中的某节点移除(包含首尾节点)
*/
void afterNodeRemoval(Node e) { // unlink
LinkedHashMap.Entry p =
(LinkedHashMap.Entry)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;
}
LinkedHashMap重写了get方法如下图所示
//获取数据
public V get(Object key) {
Node e;
if ((e = getNode(hash(key), key)) == null)
return null;
//如果设置为访问顺序来进行后续迭代
if (accessOrder)
//将当前节点移动到双向链表最后一位
afterNodeAccess(e);
return e.value;
}
LinkedHashMap也重写了,keySet(),values(),entrySet(),forEach(),内部迭代器等一些列和循环迭代的方法
/**
*获取linkedHashMap所有key
*/
public Set keySet() {
Set ks = keySet;
if (ks == null) {
ks = new LinkedKeySet();
keySet = ks;
}
return ks;
}
final class LinkedKeySet extends AbstractSet {
public final int size() { return size; }
public final void clear() { LinkedHashMap.this.clear(); }
public final Iterator iterator() {
return new LinkedKeyIterator();
}
public final boolean contains(Object o) { return containsKey(o); }
public final boolean remove(Object key) {
return removeNode(hash(key), key, null, false, true) != null;
}
public final Spliterator spliterator() {
return Spliterators.spliterator(this, Spliterator.SIZED |
Spliterator.ORDERED |
Spliterator.DISTINCT);
}
//核心代码
public final void forEach(Consumer super K> action) {
if (action == null)
throw new NullPointerException();
int mc = modCount;
//此处迭代遍历双向链表 获取节点执行对应操作
for (LinkedHashMap.Entry e = head; e != null; e = e.after)
action.accept(e.key);
if (modCount != mc)
throw new ConcurrentModificationException();
}
}
/**
*获取linkedHashMap所有values
*/
public Collection values() {
Collection vs = values;
if (vs == null) {
vs = new LinkedValues();
values = vs;
}
return vs;
}
final class LinkedValues extends AbstractCollection {
public final int size() { return size; }
public final void clear() { LinkedHashMap.this.clear(); }
public final Iterator iterator() {
return new LinkedValueIterator();
}
public final boolean contains(Object o) { return containsValue(o); }
public final Spliterator spliterator() {
return Spliterators.spliterator(this, Spliterator.SIZED |
Spliterator.ORDERED);
}
public final void forEach(Consumer super V> action) {
if (action == null)
throw new NullPointerException();
int mc = modCount;
//此处迭代遍历双向链表 获取节点执行对应操作
for (LinkedHashMap.Entry e = head; e != null; e = e.after)
action.accept(e.value);
if (modCount != mc)
throw new ConcurrentModificationException();
}
}
/**
*获取linkedHashMap所有Map中Entry
*/
public Set> entrySet() {
Set> es;
return (es = entrySet) == null ? (entrySet = new LinkedEntrySet()) : es;
}
final class LinkedEntrySet extends AbstractSet> {
public final int size() { return size; }
public final void clear() { LinkedHashMap.this.clear(); }
public final Iterator> iterator() {
return new LinkedEntryIterator();
}
public final boolean contains(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry,?> e = (Map.Entry,?>) o;
Object key = e.getKey();
Node candidate = getNode(hash(key), key);
return candidate != null && candidate.equals(e);
}
public final boolean remove(Object o) {
if (o instanceof Map.Entry) {
Map.Entry,?> e = (Map.Entry,?>) o;
Object key = e.getKey();
Object value = e.getValue();
return removeNode(hash(key), key, value, true, true) != null;
}
return false;
}
public final Spliterator> spliterator() {
return Spliterators.spliterator(this, Spliterator.SIZED |
Spliterator.ORDERED |
Spliterator.DISTINCT);
}
public final void forEach(Consumer super Map.Entry> action) {
if (action == null)
throw new NullPointerException();
int mc = modCount;
//此处迭代遍历双向链表 获取节点执行对应操作
for (LinkedHashMap.Entry e = head; e != null; e = e.after)
action.accept(e);
if (modCount != mc)
throw new ConcurrentModificationException();
}
}
// Map overrides
public void forEach(BiConsumer super K, ? super V> action) {
if (action == null)
throw new NullPointerException();
int mc = modCount;
//此处迭代遍历双向链表 获取节点执行对应操作
for (LinkedHashMap.Entry e = head; e != null; e = e.after)
action.accept(e.key, e.value);
if (modCount != mc)
throw new ConcurrentModificationException();
}
。。。。。
从上述代码可以发现这几种遍历方式都有一个公共的代码操作:该方法从头到尾会遍历整个双向链表
public final void forEach(。。。。。。) {
if (action == null)
throw new NullPointerException();
int mc = modCount;
//此处迭代遍历双向链表 获取节点执行对应操作
for (LinkedHashMap.Entry e = head; e != null; e = e.after)
。。。。。。
if (modCount != mc)
throw new ConcurrentModificationException();
}
综上所述LinkedHashMap正是因为HashMap与双向链表的存在,即可以使用HashMap提供的功能,也可以通过两种迭代方式进行循环遍历应对不同的需求场景(对于LinkedHashMap的迭代从上述源码中可以看出实际是对于双向链表的迭代,起为head尾为tail):
第一种:按照插入的顺序迭代(设置属性accessOrder=false),双向链表中节点按照是插入的顺序保存
第二种:按照访问的顺序迭代(设置属性accessOrder=true),每次对该节点进行access操作都会使该节点移动到双向链表的尾部,所以基于此特点在此双向链表尾部的数据都是最近被访问的。
LinkedHashMap还提供了一种淘汰数据的机制(由afterNodeInsertion方法提供),该机制在满足一定条件情况(该条件由使用者定义,继承LinkedHashMap重写removeEldestEntry()方法)下触发。一旦达到触发条件,那么每进行一个写入操作都会移除双向链表head节点(因为head的数据是最早写入的也就是最早存在的这也是removeEldestEntry方法名的含义“移除年老实体”)。LinkedHashMap实现LRU算法的途径之一。
public static void main(String[] args) throws Exception {
//正常按照插入的顺序迭代
LinkedHashMap linkedHashMapSort = new LinkedHashMap(6,0.75f,false);
linkedHashMapSort.put("张三",1);
linkedHashMapSort.put("李四",2);
linkedHashMapSort.put("王五",3);
linkedHashMapSort.put("李六",4);
linkedHashMapSort.put("赵七",5);
linkedHashMapSort.get("张三");
linkedHashMapSort.get("李六");
//
linkedHashMapSort.forEach((k,v)->{
System.out.println(k+"------------"+v);
});
System.out.println("----------------------------分割线--------------------------");
//按照访问的顺序迭代
LinkedHashMap linkedHashMapAccessSort = new LinkedHashMap(6,0.75f,true);
linkedHashMapAccessSort.put("张三",1);
linkedHashMapAccessSort.put("李四",2);
linkedHashMapAccessSort.put("王五",3);
linkedHashMapAccessSort.put("李六",4);
linkedHashMapAccessSort.put("赵七",5);
linkedHashMapAccessSort.get("张三");
linkedHashMapAccessSort.get("李六");
//
linkedHashMapAccessSort.forEach((k,v)->{
System.out.println(k+"------------"+v);
});
}
最终结果:
LRU算法中应用