LinkedHashMap?HashMap的“私生子”?
原文说LinkedHashMap是HashMap的子类,还引入了双向链表来维护顺序…等等,这说法太官方了! 换个角度:如果HashMap是Java Map家族里的“混沌无序之王”,那LinkedHashMap就是追求“井井有条”的秩序控!它在HashMap的骨架上,硬生生加了一条“时间线”,让你能按照插入顺序或者访问顺序来遍历数据。 就像你在硬盘上整理文件,是按时间排序还是按访问频率排序,全看你的心情!
以下是LinkedHashMap的继承与实现关系源码?别闹了,直接看它怎么“继承”HashMap的“皇位”更重要:
public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>
LinkedHashMap的“三宗罪”与“一桩美德”
-
有序?别激动,这“有序”是有代价的!LinkedHashMap用双向链表“绑架”了key-value,虽然访问起来有条不紊,但…
-
速度慢?没错!增删改查,HashMap敢说第二,没人敢说第一(除了专门优化的ConcurrentHashMap)。 LinkedHashMap?乖乖排队维护你的链表去吧!
-
底层复杂?数组+链表+红黑树+双向链表…这数据结构,简直是“俄罗斯套娃”!
那它还有啥用? 别急,LinkedHashMap的“一桩美德”足以让它在某些场景下封神:它能记住你操作数据的顺序! 这在缓存淘汰、访问日志等场景下,简直是神器!
源码?先别睡着!(JDK17版“解剖”)
Entry:HashMap.Node的“豪华升级版”
HashMap的Node是单向链表,LinkedHashMap的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); } }
简单理解:HashMap的Node是“单身宿舍”,LinkedHashMap的Entry是“豪华套间”,自带前后指针,方便“串门”!
插入新元素?没啥黑科技,就是把新“套间”扔到链表尾巴上。 访问已有节点? 如果你开启了“访问顺序模式”,那这个节点就会被“荣升”到链表尾部,享受“C位待遇”!
put?HashMap的“马仔”
void afterNodeInsertion(boolean evict) { // 在HashMap中被回调 LinkedHashMap.Entry<K,V> first; if (evict && (first = head) != null && removeEldestEntry(first)) { K key = first.key; removeNode(hash(key), key, null, false, true); } }
LinkedHashMap自己不干脏活累活,直接让HashMap干! put完之后,它负责维护链表顺序,就像一个“善后处理小组”。
get?“暗藏玄机”的访问顺序调整
public V get(Object key) { Node<K,V> e; if ((e = getNode(key)) == null) return null; if (accessOrder) afterNodeAccess(e); return e.value; }
void afterNodeAccess(Node<K,V> e) { // 移动访问节点到链表尾部 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; } }
重点是accessOrder
! 开启它,每次get都会触发afterNodeAccess
,把被访问的节点“踢”到链表尾部。 这就是LRU缓存的核心机制!
LinkedHashMap的“进化史”?一部“缝缝补补”的血泪史
JDK1.4-1.7: “青春期”的烦恼
纯数组+链表+双向链表,简单粗暴! 但hash冲突一多,链表就成了性能瓶颈。
JDK8: “中年危机”与“红黑树”的救赎
HashMap引入了红黑树,LinkedHashMap也赶紧“缝缝补补”,重写相关方法,保证链表的一致性。
JDK9+: “老年养生”与“迭代器”的安全性
增强fail-fast机制,防止多线程并发修改导致的数据不一致。
应用场景?LinkedHashMap的“高光时刻”
LRU缓存: “过期网红”的末日
public class LRUCache<K,V> extends LinkedHashMap<K,V> { private final int maxCapacity; public LRUCache(int maxCapacity) { super(maxCapacity, 0.75f, true); this.maxCapacity = maxCapacity; } @Override protected boolean removeEldestEntry(Map.Entry<K,V> eldest) { return size() > maxCapacity; } }
开启accessOrder
,重写removeEldestEntry
,当容量超过最大值时,链表头部的“老家伙”就会被无情地踢出去!
访问轨迹记录: “谁动了我的奶酪?”
LinkedHashMap<String, Long> accessLog = new LinkedHashMap<>(100, 0.75f, true) { @Override protected boolean removeEldestEntry(Map.Entry<String, Long> eldest) { return size() > 50; // 仅保留最近50次访问记录 } };
记录用户访问顺序,分析用户行为,简直是“用户画像”的利器!
性能调优?别指望它能“飞”起来
-
容量初始化: 预估你的数据量,给它一个合适的“家”,避免频繁扩容。
-
负载因子: 查询多?那就降低负载因子,减少hash冲突!
-
并发? 别碰! 除非你用
Collections.synchronizedMap
“包”起来,否则等着被坑吧! -
遍历?
entrySet().iterator()
永远比keySet()
快! 记住,永远!
LinkedHashMap vs HashMap vs TreeMap?“选妃”指南
|
|
|
|
---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- LinkedHashMap
: “秩序控”,需要记住操作顺序的场景。 - HashMap
: “糙汉子”,追求极致性能,不在乎顺序。 - TreeMap
: “强迫症”,必须按照key的顺序排列。
选哪个? 看你的需求! 别盲目追求“高大上”,适合自己的才是最好的!
黑客/
原文始发于微信公众号(龙哥网络安全):LinkedHashMap:Java界的“秩序控”,是时候重新认识它了!
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论