LinkedHashMap

LinkedHashMap 能解决什么问题?什么时候使用 LinkedHashMap?

1)LinkedHashMap 按照键值对的插入顺序进行遍历,LinkedHashMap 底层通过一个双向链表来维护 Entry 的顺序,重新插入已经存在的键,不会影响迭代顺序。
2)LinkedHashMap 的 collection 视图迭代器所需时间与映射的大小成正比,而 HashMap 迭代所需的时间与其容量成正比。
3)LinkedHashMap 返回的迭代器都是快速失败的,如果从结构上对其进行修改,除非使用迭代器自身的 remove 方法,否则迭代器将抛出 ConcurrentModificationException 异常。
4)LinkedHashMap 的遍历只跟元素个数有关,和其容量无关。

如何使用 LinkedHashMap?

1)需要维护对象间的映射关系,并且需要保证迭代顺序时,可以使用 LinkedHashMap。
2)LinkedHashMap 可以用于实现简单的 LRU 缓存。

使用 LinkedHashMap 有什么风险?

1)LinkedHashMap 需要使用额外的链表来保存元素顺序,且容量必须为 2 的幂,存在一定的内存浪费。
2)LinkedHashMap 插入时需要维护链表中元素的顺序,其插入速度比 HashMap 慢。

LinkedHashMap 核心操作的实现原理?

  • 创建实例
    /**
* HashMap.Node subclass for normal LinkedHashMap entries.
*/
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);
}
} /**
* 双向链表的头节点
*/
transient LinkedHashMap.Entry<K,V> head; /**
* 双向链表的尾节点
*/
transient LinkedHashMap.Entry<K,V> tail; /**
* 访问顺序
* 1)true,按照访问顺序遍历,每次访问节点都会将该节点移动到双向链表的尾部,最后遍历
* 2)false,按照插入顺序遍历
*/
final boolean accessOrder;
/**
* 创建一个容量为 16、加载因子为 0.75、
* 按照插入顺序进行元素遍历的 LinkedHashMap 实例
*/
public LinkedHashMap() {
super();
accessOrder = false;
} /**
* 创建一个容量为 initialCapacity、加载因子为 0.75、
* 按照插入顺序进行元素遍历的 LinkedHashMap 实例
*/
public LinkedHashMap(int initialCapacity) {
super(initialCapacity);
accessOrder = false;
} /**
* 创建一个容量为 initialCapacity、加载因子为 loadFactor、
* 按照插入顺序进行元素遍历的 LinkedHashMap 实例
*/
public LinkedHashMap(int initialCapacity, float loadFactor) {
super(initialCapacity, loadFactor);
accessOrder = false;
} /**
* 创建一个容量为 initialCapacity、加载因子为 loadFactor、
* 按照指定顺序进行元素遍历的 LinkedHashMap 实例
*/
public LinkedHashMap(int initialCapacity,
float loadFactor,
boolean accessOrder) {
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
}
  • 读取值:get、getOrDefault
    @Override
public V get(Object key) {
Node<K,V> e;
// 指定的键不存在,则返回 null
if ((e = getNode(HashMap.hash(key), key)) == null) {
return null;
}
// 如果是按照访问顺序进行遍历
if (accessOrder) {
afterNodeAccess(e);
}
return e.value;
} @Override
void afterNodeAccess(Node<K,V> e) { // move node to last
LinkedHashMap.Entry<K,V> last;
// 按照访问顺序遍历,并且当前节点不是双向链表尾节点
if (accessOrder && (last = tail) != e) {
/**
* 读取目标节点的前置节点和后置节点
* b 表示 before,目标节点的前置节点
* a 表示 after,目标节点的后置节点
*/
final LinkedHashMap.Entry<K,V> p =
(LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
// 后置节点置为 null,因为目标节点将作为新的尾节点
p.after = null;
/**
* 1)p -> tail
* 2)head -> p ->tail
*/
// 1)前置节点为 null,
if (b == null) {
// 头节点更新为目标节点的后置节点
head = a;
// 2)前置节点不为 null
} else {
// 前置节点的后置节点更新为目标节点的后置节点
b.after = a;
}
// 2)后置节点不为 null
if (a != null) {
// 更新后置节点的前置节点为目标节点的前置节点
a.before = b;
/**
* tail 节点不是目标节点,那么目标节点应该在 tail 节点之前,
* 那么它必然存在后置节点,理论上不会进入该分支
*/
} else {
last = b;
}
/**
* tail 节点不是目标节点,那么目标节点应该在 tail 节点之前,
* 那么它必然存在后置节点,理论上不会进入该分支
*/
if (last == null) {
head = p;
} else {
// 目标节点的前置节点设置为旧的 tail
p.before = last;
// 旧 tail 的后置节点设置为 目标节点
last.after = p;
}
// 目标节点设置为新的 tail
tail = p;
++modCount;
}
} /**
* 如果键不存在,则返回默认值
*/
@Override
public V getOrDefault(Object key, V defaultValue) {
Node<K,V> e;
if ((e = getNode(HashMap.hash(key), key)) == null) {
return defaultValue;
}
if (accessOrder) {
afterNodeAccess(e);
}
return e.value;
}
  • 插入值时维护链表并回调 LinkedHashMap 的 afterNodeInsertion 方法
    @Override
Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
final LinkedHashMap.Entry<K,V> p =
new LinkedHashMap.Entry<>(hash, key, value, e);
linkNodeLast(p);
return p;
} @Override
TreeNode<K,V> newTreeNode(int hash, K key, V value, Node<K,V> next) {
final TreeNode<K,V> p = new TreeNode<>(hash, key, value, next);
linkNodeLast(p);
return p;
} // 创建新节点之后,LinkedHashMap 默认会将其链接到双向链表的尾部
private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
// 读取尾节点
final LinkedHashMap.Entry<K,V> last = tail;
// 更新尾节点为新增节点
tail = p;
// 1)LinkedHashMap 原来为空
if (last == null) {
// 则设置头节点为新增节点
head = p;
} else {
// 新增节点的前置节点设置为旧尾节点
p.before = last;
// 旧尾节点的后置节点设置为新增节点
last.after = p;
}
} @Override
void afterNodeInsertion(boolean evict) { // possibly remove eldest
LinkedHashMap.Entry<K,V> first;
/**
* 1)HashMap 回调时 evict=true
* 2)如果头结点不为 null,即 LinkedHashMap 不为空
* 3)如果 removeEldestEntry 返回 true,则移除头节点
*/
if (evict && (first = head) != null && removeEldestEntry(first)) {
final K key = first.key;
removeNode(HashMap.hash(key), key, null, false, true);
}
}
  • 基于 LinkedHashMap 构建简单的 LRU 缓存
class SimpleLruCache<K,V> extends LinkedHashMap<K, V>{
private static final long serialVersionUID = -1966639797375758411L;
/**
* 默认的初始容量
*/
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
/**
* 默认的加载因子
*/
static final float DEFAULT_LOAD_FACTOR = 0.75f;
/**
* 最大元素总个数
*/
private final int maxCount; public SimpleLruCache(int maxCount) {
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, maxCount);
} public SimpleLruCache(int initialCapacity, float loadFactor, int maxCount) {
// 1)按照访问顺序遍历
super(initialCapacity, loadFactor,true);
this.maxCount = maxCount;
} public SimpleLruCache(int initialCapacity, int maxCount) {
this(initialCapacity,DEFAULT_LOAD_FACTOR , maxCount);
} /**
* 指定条件下返回 true
*/
@Override
protected boolean removeEldestEntry(java.util.Map.Entry<K, V> head) {
return maxCount < size();
}
} @Test
public void lruCache() {
final SimpleLruCache<Integer,String> cache = new SimpleLruCache<>(2);
cache.put(1, "a");
cache.put(2, "b");
cache.put(3, "c"); Assert.assertEquals(2, cache.size()); cache.get(2);
cache.put(4, "d"); Assert.assertNull(cache.get(3));
Assert.assertNotNull(cache.get(2));
}

LinkedHashMap 源码分析的更多相关文章

  1. Java集合系列[4]----LinkedHashMap源码分析

    这篇文章我们开始分析LinkedHashMap的源码,LinkedHashMap继承了HashMap,也就是说LinkedHashMap是在HashMap的基础上扩展而来的,因此在看LinkedHas ...

  2. LinkedHashMap源码分析

    hashMap源码分析:hashMap源码分析 版本说明:jdk1.7LinkedHashMap继承于HashMap,是一个有序的Map接口的实现.有序指的是元素可以按照一定的顺序排列,比如元素的插入 ...

  3. LinkedHashMap源码分析及实现LRU

    概述 从名字上看LinkedHashMap相比于HashMap,显然多了链表的实现.从功能上看,LinkedHashMap有序,HashMap无序.这里的顺序指的是添加顺序或者访问顺序. 基本使用 @ ...

  4. Java 容器 LinkedHashMap源码分析1

    同 HashMap 一样,LinkedHashMap 也是对 Map 接口的一种基于链表和哈希表的实现.实际上, LinkedHashMap 是 HashMap 的子类,其扩展了 HashMap 增加 ...

  5. 死磕 java集合之LinkedHashMap源码分析

    欢迎关注我的公众号"彤哥读源码",查看更多源码系列文章, 与彤哥一起畅游源码的海洋. 简介 LinkedHashMap内部维护了一个双向链表,能保证元素按插入的顺序访问,也能以访问 ...

  6. Java集合之LinkedHashMap源码分析

    概述 HashMap是无序的, 即put的顺序与遍历顺序不保证一样. LinkedHashMap是HashMap的一个子类, 它通过重写父类的相关方法, 实现自己的功能. 它保留插入的顺序. 如果需要 ...

  7. java Linkedhashmap源码分析

    LinkedHashMap类似于HashMap,但是迭代遍历它时,取得“键值对”的顺序是插入次序,或者是最近最少使用(LRU)的次序.只比HashMap慢一点:而在迭代访问时反而更快,因为它使用链表维 ...

  8. Java8集合框架——LinkedHashMap源码分析

    本文的结构如下: 一.LinkedHashMap 的 Javadoc 文档注释和简要说明 二.LinkedHashMap 的内部实现:一些扩展属性和构造函数 三.LinkedHashMap 的 put ...

  9. HashMap LinkedHashMap源码分析笔记

    MapClassDiagram

  10. Java 容器 LinkedHashMap源码分析2

    一.类签名 LinkedHashMap<K,V>继承自HashMap<K,V>,可知存入的节点key永远是唯一的.可以通过Android的LruCache了解LinkedHas ...

随机推荐

  1. Python 入门之 Python三大器 之 迭代器

    Python 入门之 Python三大器 之 迭代器 1.迭代器 (1)可迭代对象: <1> 只要具有__ iter __()方法就是一个可迭代对象 (我们可以通过dir()方法去判断一个 ...

  2. Spring boot项目集成Neo4j

    第一步,创建Springboot工程 使用Eclipse 创建Maven项目,并修改pom.xml文件为: <?xml version="1.0" encoding=&quo ...

  3. AFNetworking2.0源码解析<四>

    结构 AFURLResponseSerialization负责解析网络返回数据,检查数据是否合法,把NSData数据转成相应的对象,内置的转换器有json,xml,plist,image,用户可以很方 ...

  4. 前端播放m3u8格式视频

    一.前端播放m3u8格式视频 <!DOCTYPE html> <html lang="zh-CN"> <head> <meta chars ...

  5. 机器学习分类算法之K近邻(K-Nearest Neighbor)

    一.概念 KNN主要用来解决分类问题,是监督分类算法,它通过判断最近K个点的类别来决定自身类别,所以K值对结果影响很大,虽然它实现比较简单,但在目标数据集比例分配不平衡时,会造成结果的不准确.而且KN ...

  6. centos7安装mxnet

    pip install mxnet-cu90 -i http://pypi.douban.com/simple --trusted-host pypi.douban.com 安装sklearn时总报错 ...

  7. IP地址转二进制

    例如216这个数 216 = 1 * 128 +1 * 64 + 0 * 32 + 1 * 16 + 1 * 8 + 0 * 4 + 0 * 2 + 0 * 1 提取乘号之前的数字我们得到所代表的二进 ...

  8. CF839E Mother of Dragons 最大团 Bron-Kerbosch算法

    题意简述 给你一个\(n\)个节点的无向图\(G=\{V,E\}\)的邻接矩阵\(g\)和每个点的点权为\(s_i\),且\(\sum_{i=1}^n s_i = K\),要你求出\(\mathrm{ ...

  9. CF506E Mr. Kitayuta's Gift

    这道题神仙到让我面临着买不到冰皮月亮蛋糕的风险来写题解 (蛋糕真好吃呜呜呜) 这篇题解参考了CQzhangyu神仙的做法. (目测比标程科学好写) 限制是要回文,根据我们做字符串计数的常识,一定是尽量 ...

  10. XNUCA 2019ezPHP

    ezPHP 源码很简单(感觉越简单的源码越不好搞),一个写文件的功能且只能写文件名为[a-z.]* 的文件,且文件内容存在黑名单过滤,并且结尾被加上了一行,这就导致我们无法直接写入.htaccess里 ...