23、Java Map 接口实现类详解

Java Map 接口实现类详解

1. HashMap

1.1 概述

HashSet 的操作实际上是委托给一个私有的 HashMap 实例来完成的。与 HashSet 不同的是,HashMap 存储的是键值对。HashMap 提供了常量时间复杂度的 put get 操作。虽然理论上只有在没有哈希冲突时才能达到常量时间复杂度,但可以通过再哈希来控制负载因子,从而减少冲突的数量,接近常量时间复杂度。

1.2 迭代

对 HashMap 中的键或值进行迭代所需的时间与映射的容量加上键值对的数量成正比。迭代器是快速失败(fail-fast)的。

1.3 构造函数

HashMap 提供了两个构造函数,允许程序员配置新实例:

public HashMap(int initialCapacity)
public HashMap(int initialCapacity, float loadFactor)

这两个构造函数与 HashSet 的构造函数类似,允许指定初始容量,还可以选择指定再哈希时的负载因子。

2. LinkedHashMap

2.1 概述

LinkedHashMap 继承自 HashMap,它保证了迭代器返回元素的顺序。与 LinkedHashSet 不同,LinkedHashMap 提供了两种迭代顺序的选择:插入顺序或访问顺序(从最近最少使用到最近最多使用)。

2.2 构造函数

可以通过构造函数的最后一个参数来创建访问顺序的 LinkedHashMap:

public LinkedHashMap(int initialCapacity,
                     float loadFactor,
                     boolean accessOrder)

如果传入 false ,则创建插入顺序的映射。其他构造函数与 HashMap 的构造函数类似,也会创建插入顺序的映射。

2.3 应用场景 - LRU 缓存

访问顺序的 LinkedHashMap 特别适用于构建最近最少使用(LRU)缓存。在设计缓存时,关键问题是选择一种算法来决定在添加新数据时移除哪些数据以节省内存。如果选择 LRU 策略,将移除最近最少使用的条目。

2.4 removeEldestEntry 方法

LinkedHashMap 新增了 removeEldestEntry 方法,用于在添加新条目时决定是否移除最旧的条目:

protected boolean removeEldestEntry(Map.Entry<K,V> eldest)

在 LinkedHashMap 中, removeEldestEntry 方法默认不做任何操作并返回 false ,但子类可以重写该方法,在某些情况下返回 true

2.5 示例代码

以下是一个简单的示例,允许映射增长到指定的最大大小,然后在添加新条目时删除最旧的条目:

class BoundedSizeMap extends LinkedHashMap {
  private int maxEntries;
  public BoundedSizeMap(int maxEntries) {
    super(16, 0.75f, true);
    this.maxEntries = maxEntries;
  }
  protected boolean removeEldestEntry(Map.Entry eldest) {
    return size() > maxEntries;
  }
}

2.6 迭代

对 LinkedHashMap 中的键或值进行迭代所需的时间与映射中的元素数量成正比。迭代器是快速失败的。

3. WeakHashMap

3.1 概述

普通的 Map 会对其包含的所有对象保持强引用,这意味着即使键对象除了通过映射本身外无法被访问,也不会被垃圾回收。而 WeakHashMap 则不同,它通过 java.lang.ref.WeakReference 类来持有对键对象的引用。

3.2 弱引用示例

WeakReference<String> wref = new WeakReference<String>("code gui");
String recoveredStringRef = wref.get();

如果没有对字符串 "code gui" 的强引用,垃圾回收器可以回收该对象,此时 recoveredStringRef 可能为 null

3.3 应用场景

WeakHashMap 适用于当键对象是唯一的,且当外部没有对键对象的引用时,希望映射自动移除键值对的场景。例如,在内存有限的情况下,可以避免内存泄漏。

3.4 操作前检查

在执行大多数操作之前,WeakHashMap 会检查哪些键已被回收,并移除这些键值对。

3.5 性能

WeakHashMap 的性能与 HashMap 类似,但由于键的间接引用带来的额外开销,操作速度会稍慢。清除不需要的键值对的成本与需要移除的关联数量成正比。迭代器是快速失败的。

4. IdentityHashMap

4.1 概述

IdentityHashMap 与普通的 HashMap 不同,它在比较键时使用对象的身份(identity)而不是 equals 方法。这使得 IdentityHashMap 的契约与 Map 接口的契约不一致,因为 Map 接口规定使用 equals 方法进行键比较。

4.2 应用场景

IdentityHashMap 是一个专用类,常用于序列化等操作,在这些操作中需要遍历图并存储每个节点的信息。在遍历图时,需要检查每个节点是否已经被访问过,对于循环图,必须使用对象身份来检查节点是否相同。

4.3 冲突处理

IdentityHashMap 使用线性探测(linear probing)来处理哈希冲突,而不是像其他 HashSet 和 HashMap 变体那样使用链地址法(chaining)。线性探测将键和值的引用直接存储在表中的相邻位置。

4.4 构造函数

IdentityHashMap 提供了三个构造函数:

public IdentityHashMap()
public IdentityHashMap(Map<? extends K,? extends V> m)
public IdentityHashMap(int expectedMaxSize)

第三个构造函数取代了其他 HashMap 中允许用户控制表的初始容量和再哈希时负载因子的两个构造函数。IdentityHashMap 固定了负载因子(标准实现中为 0.67),以保护用户避免因不适当设置负载因子而产生的问题。

5. EnumMap

5.1 概述

EnumMap 用于实现枚举类型的映射,它的实现简单且高效。在数组实现中,每个枚举常量的序号值可以作为对应值的索引。基本的 get put 操作可以在常量时间内完成。

5.2 迭代

对 EnumMap 中的键集进行迭代所需的时间与枚举类型中常量的数量成正比,并按枚举常量的声明顺序返回键。迭代器不是快速失败的,而是弱一致的。

5.3 构造函数

EnumMap 提供了三个公共构造函数:

EnumMap(Class<K> keyType)          // 创建一个空的枚举映射
EnumMap(EnumMap<K, ? extends V> m) // 创建一个与 m 具有相同键类型和元素的枚举映射
EnumMap(Map<K, ? extends V> m)     // 使用提供的 Map 中的元素创建一个枚举映射

5.4 键类型检查

EnumMap 包含一个具体化的键类型,用于在运行时检查添加到映射中的新条目的有效性。不同的构造函数以不同的方式提供键类型。

6. SortedMap 和 NavigableMap

6.1 概述

SortedMap 接口保证了迭代器将按键的升序遍历映射。它的定义与 SortedSet 类似,提供了一些方法,如 firstKey headMap 。在 Java 6 中,SortedMap 接口被 NavigableMap 接口扩展。

6.2 SortedMap 方法

SortedMap 接口定义的额外方法可以分为三组:
| 方法组 | 方法 | 说明 |
| ---- | ---- | ---- |
| 获取第一个和最后一个元素 | K firstKey()
K lastKey() | 如果集合为空,这些操作会抛出 NoSuchElementException |
| 获取比较器 | Comparator<? super K> comparator() | 如果映射有指定的键比较器,则返回该比较器;否则返回 null |
| 查找子序列 | SortedMap<K,V> subMap(K fromKey, K toKey)
SortedMap<K,V> headMap(K toKey)
SortedMap<K,V> tailMap(K fromKey) | 这些操作与 SortedSet 中的相应操作类似,键参数不一定需要存在于映射中,返回的集合包含 fromKey (如果存在),但不包含 toKey |

6.3 排序规则

SortedMap 对其键进行排序,可以使用键的自然排序或指定的 Comparator 。但无论哪种情况, compare 方法必须与 equals 方法一致,因为 SortedMap 使用 compare 方法来确定键是否已经存在于映射中。

6.4 NavigableMap 扩展

NavigableMap 是 SortedMap 在 Java 6 中的扩展接口,提供了更多的导航方法,例如:
- lowerKey(K key) :返回小于给定键的最大键;
- higherKey(K key) :返回大于给定键的最小键;
- floorKey(K key) :返回小于或等于给定键的最大键;
- ceilingKey(K key) :返回大于或等于给定键的最小键。

这些方法使得在有序映射中查找特定键变得更加方便。

6.5 示例代码

以下是一个使用 SortedMap 和 NavigableMap 的简单示例:

import java.util.*;

public class MapExample {
    public static void main(String[] args) {
        TreeMap<Integer, String> sortedMap = new TreeMap<>();
        sortedMap.put(3, "Three");
        sortedMap.put(1, "One");
        sortedMap.put(2, "Two");

        // 使用 SortedMap 方法
        System.out.println("First key: " + sortedMap.firstKey());
        System.out.println("Last key: " + sortedMap.lastKey());

        // 使用 NavigableMap 方法
        NavigableMap<Integer, String> navigableMap = sortedMap;
        System.out.println("Lower key of 2: " + navigableMap.lowerKey(2));
        System.out.println("Higher key of 2: " + navigableMap.higherKey(2));
    }
}

在这个示例中,我们创建了一个 TreeMap (实现了 SortedMap 和 NavigableMap 接口),并向其中添加了一些键值对。然后,我们使用 SortedMap 和 NavigableMap 的方法来获取特定的键。

7. 不同 Map 实现类的比较

7.1 性能比较

Map 实现类 插入操作 查找操作 迭代操作 内存占用 适用场景
HashMap 平均 O(1),最坏 O(n) 平均 O(1),最坏 O(n) O(capacity + size) 中等 通用场景,不需要保持顺序
LinkedHashMap 平均 O(1),最坏 O(n) 平均 O(1),最坏 O(n) O(size) 稍高 需要保持插入顺序或访问顺序
WeakHashMap 平均 O(1),最坏 O(n) 平均 O(1),最坏 O(n) O(capacity + size) 稍低 键对象可能被垃圾回收的场景
IdentityHashMap 平均 O(1),最坏 O(n) 平均 O(1),最坏 O(n) O(capacity + size) 中等 需要使用对象身份比较键的场景
EnumMap O(1) O(1) O(enum constants) 键为枚举类型的场景
SortedMap (TreeMap) O(log n) O(log n) O(n) 较高 需要按键排序的场景

7.2 选择建议

  • 如果需要快速插入、查找和删除操作,并且不需要保持顺序,优先选择 HashMap
  • 如果需要保持插入顺序或访问顺序,选择 LinkedHashMap
  • 如果键对象可能被垃圾回收,使用 WeakHashMap 可以避免内存泄漏。
  • 如果需要使用对象身份比较键,选择 IdentityHashMap
  • 如果键为枚举类型, EnumMap 是最佳选择。
  • 如果需要按键排序,使用 SortedMap 的实现类,如 TreeMap

8. 总结

Java 提供了多种 Map 接口的实现类,每个实现类都有其独特的特点和适用场景。在选择合适的 Map 实现类时,需要考虑以下因素:
- 性能要求:插入、查找和迭代操作的时间复杂度。
- 顺序要求:是否需要保持插入顺序或按键排序。
- 内存占用:不同实现类的内存使用情况。
- 键的特性:键是否为枚举类型、是否需要使用对象身份比较等。

通过了解这些 Map 实现类的特点,我们可以根据具体的需求选择最合适的实现类,从而提高程序的性能和效率。

下面是一个简单的流程图,展示了选择合适 Map 实现类的决策过程:

graph TD
    A[是否需要顺序?] -->|是| B[是否需要访问顺序?]
    A -->|否| C[键是否为枚举类型?]
    B -->|是| D[LinkedHashMap (访问顺序)]
    B -->|否| E[LinkedHashMap (插入顺序) / SortedMap]
    C -->|是| F[EnumMap]
    C -->|否| G[键是否可能被垃圾回收?]
    G -->|是| H[WeakHashMap]
    G -->|否| I[是否需要对象身份比较?]
    I -->|是| J[IdentityHashMap]
    I -->|否| K[HashMap]

这个流程图可以帮助我们在实际应用中快速选择合适的 Map 实现类。希望本文对大家理解 Java 中的 Map 接口实现类有所帮助。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值