数据结构 - LinkedHashMap(二)

151 阅读2分钟

1. 基本结构

public class LinkedHashMap<K,V> extends HashMap<K,V> {
    // 双向链表的头尾节点
    transient LinkedHashMap.Entry<K,V> head;    // 头节点
    transient LinkedHashMap.Entry<K,V> tail;    // 尾节点
    
    // 节点定义
    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);
        }
    }
}

2. 数据结构图解

graph LR
    A[HashMap数组] --> B[链表/红黑树]
    C[双向链表] --> D[按插入顺序]
    
    subgraph "HashMap结构"
    A --> E[桶0]
    A --> F[桶1]
    A --> G[桶2]
    end
    
    subgraph "双向链表"
    H[头节点] --> I[节点1]
    I --> J[节点2]
    J --> K[尾节点]
    K --> J
    J --> I
    I --> H
    end

3. 插入过程

// 1. 继承自HashMap的put方法
public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}

// 2. 在节点插入后,会调用afterNodeInsertion方法
void afterNodeInsertion(boolean evict) {
    LinkedHashMap.Entry<K,V> first;
    // 维护插入顺序
    if (evict && (first = head) != null) {
        removeEldestEntry(first);
    }
}

4. 实际例子

LinkedHashMap<String, String> map = new LinkedHashMap<>();

// 1. 插入元素
map.put("A", "1");  // 第一个
map.put("B", "2");  // 第二个
map.put("C", "3");  // 第三个

// 2. 遍历 - 会按插入顺序输出
for (Map.Entry<String, String> entry : map.entrySet()) {
    System.out.println(entry.getKey() + ":" + entry.getValue());
}
// 输出:
// A:1
// B:2
// C:3

5. 两种结构的配合

graph TD
    A[LinkedHashMap] --> B[HashMap结构]
    A --> C[双向链表]
    
    B --> D[快速查找]
    C --> E[维护顺序]
    
    subgraph "职责分工"
    D --> F[O1时间复杂度访问]
    E --> G[记录插入/访问顺序]
    end

6. 特殊功能

6.1 按访问顺序排序

// 创建按访问顺序排序的LinkedHashMap
LinkedHashMap<String, String> accessMap = 
    new LinkedHashMap<>(16, 0.75f, true);

accessMap.put("A", "1");
accessMap.put("B", "2");
accessMap.put("C", "3");

accessMap.get("A");  // 访问A
// 现在A会移到最后

// 遍历顺序:B -> C -> A

7. LRU缓存实现

class LRUCache<K,V> extends LinkedHashMap<K,V> {
    private final int capacity;
    
    public LRUCache(int capacity) {
        super(capacity, 0.75f, true);  // 按访问顺序
        this.capacity = capacity;
    }
    
    @Override
    protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
        return size() > capacity;  // 超过容量则移除最老的
    }
}

8. 实际应用场景

// 1. 需要记录插入顺序
LinkedHashMap<String, User> userMap = new LinkedHashMap<>();
userMap.put("user1", new User("张三"));
userMap.put("user2", new User("李四"));
// 遍历时保证按插入顺序

// 2. LRU缓存
LRUCache<String, Data> cache = new LRUCache<>(100);
cache.put("key1", data1);
cache.get("key1");  // 访问会更新顺序

9. 性能特点

1. 查找性能:O(1),继承自HashMap
2. 插入性能:O(1),但比HashMap略慢(需要维护链表)
3. 内存消耗:比HashMap大(额外的链表指针)

10. 总结配合方式

  1. HashMap 负责:

    • 存储节点
    • 快速查找
    • 哈希计算
  2. 双向链表负责:

    • 维护顺序
    • 快速插入/删除
    • 迭代顺序

就像是:

  • HashMap是一个仓库(负责存储)
  • 双向链表是一个记录本(负责记录顺序)
  • 两者配合提供了"有序的Map"功能

这种组合让 LinkedHashMap 既拥有了 HashMap 的高效查找,又能维护元素的顺序,是一个非常精妙的设计。