深入解析 TreeMap 和 LinkedHashMap:底层实现与使用场景

在 Java 中,TreeMapLinkedHashMap 是非常常用的集合类,它们在存储元素、顺序控制以及性能方面各有特点。通过了解它们的底层实现和使用场景,可以帮助我们更好地选择合适的数据结构。在本文中,我们将深入剖析这两者的基本原理、源码实现以及使用场景,帮助你在开发中做出明智选择。

一、TreeMap:基于红黑树的有序映射

1.1 TreeMap 的基本结构

TreeMap 是 Java 中实现 NavigableMap 接口的一个类,它的底层实现基于 红黑树。红黑树是一种自平衡的二叉查找树,它能够高效地实现对元素的查找、插入和删除操作。在 TreeMap 中,元素是根据 键的自然顺序 或者 外部比较器 进行排序的。

TreeMap 的元素的插入和查找时间复杂度为 O(log n),因此它适用于需要根据 键排序 的场景。

private final Comparator<? super K> comparator;
private transient Entry<K,V> root;
private transient int size = 0;
private transient int modCount = 0;

static final class Entry<K,V> implements Map.Entry<K,V> {
    K key;
    V value;
    Entry<K,V> left;
    Entry<K,V> right;
    Entry<K,V> parent;
}

1.2 TreeMapput 方法

TreeMapput 方法根据红黑树的性质,依照键的大小关系,将新的键值对插入到树中。插入时,会根据键值使用 外部比较器 或者 内部的 compareTo 方法 来确定新节点应该放置的位置。如果树中已经存在相同的键,则更新对应的值。

public V put(K key, V value) {
    Entry<K,V> t = root;
    if (t == null) {
        compare(key, key); // type check
        root = new Entry<>(key, value, null);
        size = 1;
        modCount++;
        return null;
    }
    
    int cmp;
    Entry<K,V> parent;
    Comparator<? super K> cpr = comparator;

    if (cpr != null) {
        do {
            parent = t;
            cmp = cpr.compare(key, t.key);
            if (cmp < 0)
                t = t.left;
            else if (cmp > 0)
                t = t.right;
            else
                return t.setValue(value);
        } while (t != null);
    } else {
        if (key == null) throw new NullPointerException();
        Comparable<? super K> k = (Comparable<? super K>) key;
        do {
            parent = t;
            cmp = k.compareTo(t.key);
            if (cmp < 0)
                t = t.left;
            else if (cmp > 0)
                t = t.right;
            else
                return t.setValue(value);
        } while (t != null);
    }

    Entry<K,V> e = new Entry<>(key, value, parent);
    if (cmp < 0)
        parent.left = e;
    else
        parent.right = e;

    fixAfterInsertion(e);
    size++;
    modCount++;
    return null;
}

1.3 TreeMap 的特点

  • 排序方式TreeMap 按照键的自然顺序或提供的比较器进行排序。
  • 时间复杂度:插入、删除、查找的时间复杂度都是 O(log n)
  • 键不可为 nullTreeMap 不允许 null 键,如果尝试插入 null 键会抛出 NullPointerException

二、LinkedHashMap:保持插入顺序或访问顺序的 HashMap

2.1 LinkedHashMap 的基本结构

LinkedHashMap 继承自 HashMap,它通过在每个元素之间维护 双向链表 来维护元素的顺序。通过这种方式,LinkedHashMap 可以按 插入顺序访问顺序 来迭代元素。LinkedHashMap 支持两种顺序:

  • 插入顺序:元素的插入顺序保持不变,修改操作不影响顺序。
  • 访问顺序:每次访问元素时,该元素会被移动到链表的末尾,从而实现最近最常访问的元素排在链表末尾。

2.2 插入顺序与访问顺序

LinkedHashMap<String, String> map = new LinkedHashMap<>();
map.put("1", "one");
map.put("2", "two");
map.put("3", "three");
System.out.println(map);  // 输出:{1=one, 2=two, 3=three}

map.get("2");
System.out.println(map);  // 输出:{1=one, 3=three, 2=two}  (访问顺序改变)

2.3 LinkedHashMap 适配 LRU 缓存

LinkedHashMap 还可以被用来实现一个简单的 LRU(最近最少使用)缓存。通过重写 removeEldestEntry 方法,当元素超过缓存最大容量时,可以自动移除最久未使用的元素。

LinkedHashMap<String, String> map = new LinkedHashMap<>(16, 0.75f, true) {
    @Override
    protected boolean removeEldestEntry(Map.Entry eldest) {
        return size() > 3;
    }
};
map.put("1", "one");
map.put("2", "two");
map.put("3", "three");
map.get("1");
map.put("4", "four");
System.out.println(map);  // 输出:{3=three, 1=one, 4=four}  (自动移除最久未访问的元素)

2.4 LinkedHashMap 的基本属性

LinkedHashMap 类中有两个重要的属性用于管理双向链表:

static class Entry<K, V> extends HashMap.Node<K, V> {
    Entry<K,V> before, after;
}

transient LinkedHashMap.Entry<K,V> head;
transient LinkedHashMap.Entry<K,V> tail;
final boolean accessOrder;
  • headtail:分别表示双向链表的头部和尾部。
  • accessOrder:控制顺序的方式,true 表示按访问顺序排序,false 表示按插入顺序排序。

2.5 LinkedHashMap 关键方法

  • putget 方法:通过继承 HashMap,这些方法可以正常使用,同时可以在访问时更新元素的顺序。
  • afterNodeAccess:当元素被访问时,更新元素在链表中的位置。
  • afterNodeInsertion:在元素插入后,如果需要移除最久未使用的元素,会调用 removeEldestEntry 方法。

三、总结

3.1 TreeMapLinkedHashMap 的比较

特性TreeMapLinkedHashMap
底层数据结构红黑树 (自平衡二叉查找树)哈希表 + 双向链表
元素排序有序,按键的自然顺序或自定义比较器无序(插入顺序)或按访问顺序(通过 accessOrder 控制)
时间复杂度O(log n)O(1)
null 键不允许允许 null 键
使用场景需要排序的键值对需要保持元素顺序(插入或访问顺序)

3.2 使用建议

  • TreeMap 适合用在需要排序的场景,比如需要按照键值排序、查找范围元素等。
  • LinkedHashMap 适合用在需要维护元素插入顺序或者访问顺序的场景,尤其是需要实现 LRU 缓存 时非常有用。

了解这两者的底层实现和应用场景,可以帮助我们在开发中做出更合适的选择,充分利用它们各自的优势。

🌟 关注我的CSDN博客,收获更多技术干货! 🌟

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

dm菜鸟编程

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值