惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

人人都是产品经理
人人都是产品经理
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
P
Privacy International News Feed
Simon Willison's Weblog
Simon Willison's Weblog
I
Intezer
Spread Privacy
Spread Privacy
The Hacker News
The Hacker News
P
Palo Alto Networks Blog
TaoSecurity Blog
TaoSecurity Blog
S
Secure Thoughts
Google Online Security Blog
Google Online Security Blog
H
Heimdal Security Blog
N
News | PayPal Newsroom
Attack and Defense Labs
Attack and Defense Labs
Recent Commits to openclaw:main
Recent Commits to openclaw:main
博客园 - 【当耐特】
Webroot Blog
Webroot Blog
小众软件
小众软件
Help Net Security
Help Net Security
D
Darknet – Hacking Tools, Hacker News & Cyber Security
N
News and Events Feed by Topic
Hacker News - Newest:
Hacker News - Newest: "LLM"
PCI Perspectives
PCI Perspectives
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
The Cloudflare Blog
Cloudbric
Cloudbric
AI
AI
WordPress大学
WordPress大学
博客园 - 聂微东
Jina AI
Jina AI
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
博客园 - 三生石上(FineUI控件)
Hacker News: Ask HN
Hacker News: Ask HN
H
Hacker News: Front Page
博客园 - Franky
V
V2EX
Schneier on Security
Schneier on Security
G
GRAHAM CLULEY
S
SegmentFault 最新的问题
有赞技术团队
有赞技术团队
H
Help Net Security
量子位
S
Security @ Cisco Blogs
大猫的无限游戏
大猫的无限游戏
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Recorded Future
Recorded Future
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
J
Java Code Geeks
C
Cisco Blogs
S
Security Affairs

博客园 - cacard

Android中的内部类引起的内存泄露 Android的消息机制: Message/MessageQueue/Handler/Looper ArrayList/Vector的原理、线程安全和迭代Fail-Fast JVM中的Stack和Frame JVM中的垃圾收集算法和Heap分区简记 无锁编程以及CAS 简述Java内存模型的由来、概念及语义 MQTT协议简记 RabbitMQ的工作队列和路由 RabbitMQ 入门 [Java] HashMap 源码简要分析 [Java] Hashtable 源码简要分析 CentOS 安装 Hadoop 手记 树莓派(RespberryPi)安装手记 RPC简述 [C++] const与重载 [C++] 左值、右值、右值引用 [C++] 引用 Java线程池 / Executor / Callable / Future
[Java] LinkedHashMap 源码简要分析
cacard · 2014-03-08 · via 博客园 - cacard

 特点

* 各个元素不仅仅按照HashMap的结构存储,而且每个元素包含了before/after指针,通过一个头元素header,形成一个双向循环链表。使用循环链表,保存了元素插入的顺序。

* 可设置参数,让每次get()后的元素排在双向链表的最后。

Entry类

private static class Entry<K,V> extends HashMap.Entry<K,V> // 继承自HashMap的Entry(已有key/value/hash/next字段)
{
     // 双向链表
     Entry<K,V> before;
     Entry<K,V> after;

     // 构造函数
     Entry(int hash, K key, V value, HashMap.Entry<K,V> next) {
            super(hash, key, value, next);
     }
     
     // 删除当前结点
     private void remove(){
          before.after = after;
          after.before = before;
     }

     // 在当前结点的前面添加结点
     private void addBefore(Entry<K,V> existingEntry){
            after  = existingEntry;
            before = existingEntry.before;
            before.after = this;
            after.before = this;          
     }

     // 如果设定某个参数,get()命中后,把当前元素放到链表最后
     void recoreAccess(HashMap<K,V> m) {
          LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;     // 把调用者的this转化成LinkedHashMap
          if(lm.accessOrder){
               remove(); // 删除当前结点
               addBefore(lm.header); // 插入到header前面
          }
     }
}

源码简要分析

public class LinkedHashMap<K,V> extends HashMap<K,V>
{
     private Entry<K,V> header; // 双向链表的头部。
     private final boolean accessOrder; // 默认false。如果true表示get()命中后,把当前元素放到链表最后。

     // init()
    void init() {
        header = new Entry<>(-1, null, null, null);
        header.before = header.after = header; // 双向链表首尾相连
    }

     // put() 继承自 HashMap
     // 添加元素时,不仅按照HashMap的方式散列存储,而且还通过双向链表记录先后顺序
     public V put(K key, V value) { 
          int hash = hash(key.hashCode());     // key的特殊hash值
          int i = indexFor(hash,table.length);     // 槽位 index

          // key是否已经存在,存在则返回value。
          for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this); //LinkedHashMap特有
                return oldValue;
            }
              }

               // key不存在就添加Entry
               addEntry(hash,key,value,i);
               return null;
     }

    void addEntry(int hash, K key, V value, int bucketIndex) {
        createEntry(hash, key, value, bucketIndex);

        // Remove eldest entry if instructed, else grow capacity if appropriate
        Entry<K,V> eldest = header.after;
        if (removeEldestEntry(eldest)) {
            removeEntryForKey(eldest.key);
        } else {
            if (size >= threshold)
                resize(2 * table.length);
        }
    }

    void createEntry(int hash, K key, V value, int bucketIndex/*槽位index*/) {
        HashMap.Entry<K,V> old = table[bucketIndex];
        Entry<K,V> e = new Entry<>(hash, key, value, old);
        table[bucketIndex] = e;
        e.addBefore(header);
        size++;
    }

     // get() 
     // 取元素是按照散列而不是双向链表进行查找,速度快
    public V get(Object key) {
        Entry<K,V> e = (Entry<K,V>)getEntry(key); // getEntry()使用了HashMap的getEntry,即按照HashMap的方式寻找元素。
        if (e == null)
            return null;
        e.recordAccess(this); 
        return e.value;
    }

}

添加元素的过程示意图

初始化时,头元素header的before/after均指向自身。

插入元素e1后,header的before/after均指向e1;e1的before/after均指向header。

插入元素e2后,header的after继续指向e1,e1的after指向e2,e1的before指向header。header的before指向e2。e2的before指向e1,e2的after指向header。