面试题
- LinkedHashMap如何实现有序的
- 如何用LinkedHashMap实现LRU
不想看源码解析的同学,可以直接去最下方查看答案
源码解析
LinkedHashMap在Map的基础上进行了扩展,提供了按序访问的能力。这个顺序通过accessOrder控制,可以是结点的插入顺序,也可以是结点的访问时间顺序。
LinkedHashMap还提供了removeEldestEntry方法,可以用来删除最老访问结点。
通过accessOrder和removeEldestEntry可以用来实现LRU缓存。
如图所示,LinkedHashMap实现顺序访问的方法比较简单,在HashMap实现之外,还维护了一个双向链表。每当插入结点时,不仅要在Map中维护,还需要在链表中进行维护。HashMap中的put, get等方法都提供了一些钩子方法,如afterNodeAccess
、afterNodeInsertion
和afterNodeRemoval
等。通过这些方法,LinkedHashMap可以对这些结点进行一些特性化的维护。
当遍历LinkedHashMap时通过遍历链表代替遍历Map中的各个槽,从而实现按序访问。
底层数据结构
1 | 复制代码 /** |
Node结点
1 | 复制代码 Node<K, V> newNode(int hash, K key, V value, Node<K, V> e) { |
工具方法
1 | 复制代码 // 在双向链表尾部添加结点 |
构造方法
1 | 复制代码 public LinkedHashMap(int initialCapacity, float loadFactor) { |
钩子方法
1 | 复制代码 // 重写HashMap中提供给LinkedHashMap的钩子方法 |
其他
1 | 复制代码 public boolean containsValue(Object value) { |
迭代器
1 | 复制代码 abstract class LinkedHashIterator { |
面试题解答
- LinkedHashMap如何实现有序的
LinkedHashMap在HashMap的基础上,还将每个key-value对应的Node维护在了一个额外的双向链表中。
LinkedHashMap通过accessOrder可以支持按插入的顺序访问,或者按遍历的顺序访问
accessOrder
* false: 按插入顺序排序,map中每插入一个结点时,将这个结点同时放置在双向链表的结尾 * true: 按访问顺序排序,当操作map中的一个结点时,通过HashMap提供的钩子方法(`afterNodeAccess`、`afterNodeInsertion`和`afterNodeRemoval`)找到这个结点在链表中的位置,并移动到链表结尾。这样链表的头结点就是链表最久没有访问过的结点
遍历的时候,通过便利双向链表代替遍历map的每个槽,来实现顺序访问。
2. 如何用LinkedHashMap实现LRU
首先分析LRU算法有哪些特性
1. 新数据插入到链表尾部(代表最新访问); 2. 每当缓存命中(即缓存数据被访问)则将数据移到链表尾部(代表最新访问); 3. 当链表满的时候,将链表头部的数据丢弃(删除最久未访问结点);
在LinkedHashMap保证结点有序的情况下,通过设置accessOrder为true,采用按遍历顺序维护结点。
1. put方法将结点插入到双向链表尾部实现LRU特性 1; 2. 钩子方法`afterNodeAccess`实现LRU特性 2; 3. 实现removeEldestEntry方法,删除最久未访问结点。实现LRU特性 3;
本文转载自: 掘金