Java 集合:IdentityHashMap 类

IdentityHashMap 是 Java 集合框架中一个特殊的 Map 实现,它与 HashMap 类似,但在判断键(或值)是否相等时采用引用相等性(==)而非对象相等性(equals () 方法)。这种特性使它在特定场景中具有独特用途。

IdentityHashMap 重要特性

  1. 相等性判断:

    • 键的比较使用 == 而非 equals(),即只有当两个键是同一个对象(引用相同)时才被视为相等。

    • 值的比较同样使用 ==,但实际使用中通常更关注键的比较逻辑。

  1. 存储结构:

    • 内部采用哈希表(数组 + 链表)实现,但哈希值计算基于键的系统 identity hash code(通过 System.identityHashCode(Object) 获得)。

    • 与 HashMap 不同,IdentityHashMap 不使用键对象自身的 hashCode() 方法。

  1. 空值支持:

    • 允许键和值为 null,且多个 null 键会被视为不同(因为 null == null 结果为 true,所以实际只会存储一个 null 键)。

  1. 非线程安全:

    • 本身不是线程安全的,多线程环境下需手动同步(如使用 Collections.synchronizedMap())。

  1. 无序性:

    • 迭代顺序不确定,且不保证与插入顺序或其他顺序一致。

  

IdentityHashMap 构造方法

IdentityHashMap 提供了以下构造方法:

  • IdentityHashMap()  创建一个默认初始容量(21)的空 IdentityHashMap。

  • IdentityHashMap(int expectedMaxSize)  创建一个指定预期最大容量的空 IdentityHashMap,容量会自动调整为大于等于该值的最小质数。

  • IdentityHashMap(Map<? extends K, ? extends V> m)  从指定 Map 复制所有键值对到新的 IdentityHashMap 中,键的比较遵循 IdentityHashMap 的规则。

  

IdentityHashMap 常用方法

IdentityHashMap 实现了 Map 接口的所有方法,核心方法与普通 Map 类似:

V put(K key, V value)

添加键值对,若键已存在(基于引用相同)则替换其值。判断 “键是否存在” 时,使用 == 而非 equals()。返回被替换的旧值(若键不存在则返回 null)。注意:null 键是允许的,且所有 null 引用被视为同一个键(因为 null == null 为 true)。例如:

String a = new String("key");
String b = new String("key");
map.put(a, 1);
map.put(b, 2); // 由于 a != b(引用不同),会添加新键值对,而非替换

void putAll(Map<? extends K, ? extends V> m)

批量添加另一个映射 m 中的所有键值对到当前 IdentityHashMap。对 m 中的每个键值对调用 put 方法,因此键的存在性判断仍基于 ==。若 m 中存在多个引用不同但 equals() 相等的键,会全部添加到当前映射中。

V get(Object key)

根据键获取对应的值,仅当存在与 key 引用相同(==)的键时才返回对应值。如果找到引用相同的键则返回对应值,否则返回 null。示例:

String a = new String("key");
String b = new String("key");
map.put(a, 1);
map.get(b); // 返回 null(因为 a != b)
map.get(a); // 返回 1(引用相同)

Set<K> keySet()

返回所有键的 Set 视图。集合中的键是原映射中所有键的引用(不复制),且集合支持迭代、删除操作(通过迭代器的 remove() 方法删除会同步影响原映射)。迭代顺序未指定,且可能随映射的修改而变化。

Collection<V> values()

返回所有值的 Collection 视图。与 keySet() 类似,值是原映射中值的引用,支持迭代和删除(通过迭代器删除会移除对应键值对)。判断值是否存在于集合中时,基于 == 而非 equals()(与 containsValue 逻辑一致)。

Set<Map.Entry<K, V>> entrySet()

返回所有键值对(Entry)的 Set 视图。

Entry 的 getKey() 和 getValue() 返回原键和值的引用;setValue(V value) 会替换对应的值,且新值的设置不影响键的判断(仍基于引用)。迭代时,Entry 的顺序未指定,且修改映射(如 put、remove)可能导致迭代行为不确定(非快速失败,与 HashMap 不同)。

V remove(Object key)

删除与 key 引用相同(==)的键对应的键值对。该方法将返回被删除的值(若键不存在则返回 null)。示例:

String a = new String("key");
String b = new String("key");
map.put(a, 1);
map.remove(b); // 无效果(a != b)
map.remove(a); // 删除键 a 对应的键值对,返回 1

void clear()

清空映射中所有键值对,调用后 size() 为 0,isEmpty() 为 true。

boolean containsKey(Object key)

判断映射中是否存在与 key 引用相同(==)的键。如果存在则返回 true,否则返回 false(包括 key 为 null 且映射中无 null 键的情况)。

boolean containsValue(Object value)

判断映射中是否存在与 value 引用相同(==)的值。

与普通 Map 差异:普通 Map(如 HashMap)通过 equals() 判断值是否存在,而 IdentityHashMap 严格使用 ==。例如:

Integer a = new Integer(1);
Integer b = new Integer(1);
map.put("k", a);
map.containsValue(b); // 返回 false(a != b)
map.containsValue(a); // 返回 true

int size()

返回映射中键值对的数量,基于当前实际存储的条目数(不受引用是否被回收影响,因键值均为强引用)。

boolean isEmpty()

判断映射是否为空,即 size() == 0 时返回 true,否则返回 false。

  

IdentityHashMap 简单示例

下面将通过简单示例介绍 IdentityHashMap 的基础用法:

package com.hxstrive.java_collection.identityHashMap;

import java.util.IdentityHashMap;

public class IdentityHashMapExample {
    public static void main(String[] args) {
        // 创建IdentityHashMap
        IdentityHashMap<String, Integer> map = new IdentityHashMap<>();
        
        String s1 = new String("key");
        String s2 = new String("key");
        
        // s1和s2内容相同但引用不同,视为不同键
        map.put(s1, 1);
        map.put(s2, 2);
        
        System.out.println("大小:" + map.size());  // 输出:2
        System.out.println("s1对应值:" + map.get(s1));  // 输出:1
        System.out.println("s2对应值:" + map.get(s2));  // 输出:2
        System.out.println("新对象\"key\"对应值:" + map.get(new String("key")));  // 输出:null
        
        // 键为同一对象时才会覆盖
        String s3 = s1;
        map.put(s3, 3);
        System.out.println("s1对应值(覆盖后):" + map.get(s1));  // 输出:3
    }
}

运行结果:

大小:2
s1对应值:1
s2对应值:2
新对象"key"对应值:null
s1对应值(覆盖后):3

  

IdentityHashMap 原理分析

基于对象身份的键比较

IdentityHashMap 中,两个键 k1 和 k2 被视为相等,当且仅当 k1 == k2(即内存地址相同),而不依赖 k1.equals(k2) 的结果。下面可以通过分析 get() 方法源码来查看:

/**
 * 返回指定键所映射的值,如果映射不存在则返回{@code null}
 * 
 * <p>具体来说,当且仅当此映射包含键{@code k}到值{@code v}的映射,且满足{@code (key == k)}(引用相等)
 * 时,返回{@code v};否则返回{@code null}(最多只会有一个这样的映射)
 * 
 * <p>返回{@code null}并不一定表示映射中没有该键的映射,也可能是该键明确映射到{@code null}。
 * 可以通过{@link #containsKey containsKey}方法区分这两种情况
 *
 * @see #put(Object, Object)
 */
@SuppressWarnings("unchecked")
public V get(Object key) {
    // 处理null键:将null转换为一个特殊的空对象标记(maskNull内部实现)
    // 因为数组中不能存储null作为键标识,需用特殊对象替代
    Object k = maskNull(key);
    // 获取哈希表数组引用(避免多线程下的内存可见性问题,使用局部变量缓存)
    Object[] tab = table;
    int len = tab.length;
    // 计算当前键在哈希表中的初始索引
    int i = hash(k, len);
    
    // 循环查找匹配的键(处理哈希冲突)
    while (true) {
        Object item = tab[i];
        // 找到匹配的键(引用相等),返回其对应的 value(存储在 i+1 位置)
        if (item == k) // 【这里,引用比较,而非值比较】
            return (V) tab[i + 1];
        // 遇到空位置,说明哈希链结束,没有找到匹配的键
        if (item == null)
            return null;
        // 哈希冲突:使用线性探测法寻找下一个可能的索引位置
        i = nextKeyIndex(i, len);
    }
}

数据结构:线性探测哈希表

IdentityHashMap 底层采用 哈希表 + 线性探测法 解决哈希冲突,而非 HashMap 的 “数组 + 链表 / 红黑树” 结构。具体结构如下:

  • 底层数组:使用一个 Object[] 数组(名为 table)存储键值对,数组长度为偶数,键和值交替存放(索引 i 存键,i+1 存对应值)。

  • 容量:默认初始容量为 32(可通过构造函数指定),容量必须是 2 的幂(与 HashMap 类似,便于哈希计算)。

  • 负载因子:默认负载因子为 2/3,当元素数量超过 容量 * 负载因子 时,触发扩容(容量翻倍)。

源码如下:

/**
 * @param <K> 键的类型
 * @param <V> 值的类型
 */
public class IdentityHashMap<K,V>
    extends AbstractMap<K,V>
    implements Map<K,V>, java.io.Serializable, Cloneable
{
    /**
     * 无参构造器使用的初始容量
     * 必须是2的幂。值32对应的是在负载因子为2/3时,预期的最大容量为21
     */
    private static final int DEFAULT_CAPACITY = 32;

    /**
     * 最小容量,当构造器传入的容量小于此值时使用
     * 值4对应的是在负载因子为2/3时,预期的最大容量为2
     * 必须是2的幂
     */
    private static final int MINIMUM_CAPACITY = 4;

    /**
     * 最大容量,当构造器传入的容量大于此值时使用
     * 必须是2的幂且不大于2^29
     * 
     * 实际上,此映射最多可存储MAXIMUM_CAPACITY-1个键值对,
     * 因为必须至少保留一个空槽(key == null)以避免get()、put()、remove()方法出现无限循环
     */
    private static final int MAXIMUM_CAPACITY = 1 << 29;

    /**
     * 存储键值对的哈希表数组,必要时会进行扩容
     * 数组长度必须始终是2的幂(便于通过位运算高效计算索引)
     */
    transient Object[] table;

    /**
     * 此映射中包含的键值对数量
     * @serial 用于序列化
     */
    int size;

    /**
     * 修改次数计数器,用于支持快速失败(fast-fail)迭代器
     * 当映射结构被修改时自增,迭代器会检查此值以检测并发修改
     */
    transient int modCount;

    /**
     * 用于在哈希表中表示null键的特殊对象
     * 因为数组中不能直接存储null作为键(会与空槽混淆),故使用此对象替代
     */
    static final Object NULL_KEY = new Object();

    //....
}

IdentityHashMap 的底层存储是一个数组(table),其长度(容量)必须是2 的幂(便于通过位运算快速计算索引)。为了避免哈希冲突过于频繁,当存储的键值对数量(size)达到某个阈值时,会触发扩容。这个阈值由 容量 × 负载因子 计算得出。

IdentityHashMap的默认负载因子是 2/3(这是 JDK 内部固定的,不允许外部修改),意味着:当键值对数量超过 容量 ×2/3 时,会触发扩容。

对 DEFAULT_CAPACITY = 32 的理解

无参构造器创建IdentityHashMap时,初始容量默认为 32。

为什么是 32?因为 32 是 2 的幂(满足容量必须为 2 的幂的要求),且结合负载因子 2/3 计算:32 × 2/3 ≈ 21.33,向下取整后约为 21。这意味着,当使用无参构造器时,IdentityHashMap在存储不超过 21 个键值对时,无需扩容,能保持较好的性能(冲突少、查询快)。因此注释中说 “值 32 对应的是在负载因子为 2/3 时,预期的最大容量为 21”。

对 MINIMUM_CAPACITY = 4 的理解

当用户通过有参构造器指定初始容量时,如果指定的值小于 4,则强制使用 4 作为初始容量(这是最小允许的容量)。

为什么是 4?4 是 2 的幂(满足容量要求),结合负载因子 2/3 计算:

4 × 2/3 ≈ 2.67,向下取整后约为 2。

这意味着,即使使用最小容量 4,IdentityHashMap 也能在存储不超过 2 个键值对时无需扩容。

因此注释中说 “值 4 对应的是在负载因子为 2/3 时,预期的最大容量为 2”。

IdentityHashMap 源码中哪里体现了负载因子 2/3?

在 IdentityHashMap 的源码中,负载因子 2/3 并未通过显式常量定义,而是直接体现在扩容阈值的计算逻辑中。当键值对数量(size)超过当前容量的 2/3 时,会触发哈希表扩容。

IdentityHashMap 在添加键值对(put 方法)时,会检查当前元素数量是否达到扩容阈值(容量 × 2/3),如果达到则触发扩容。关键代码如下:

public V put(K key, V value) {
    //...
    while (true) {
        //...
        if (item == null) { // 找到空位,插入新键值对
            modCount++;
            tab[i] = k;
            tab[i + 1] = value;
            size++;
            // 核心:判断是否需要扩容(当前 size 超过容量的 2/3)
            if (size >= len / 3 * 2) { 
                resize(len * 2); // 扩容为原容量的 2 倍(保持 2 的幂)
            }
            return null;
        }
        //...
    }
}

键的哈希值计算

IdentityHashMap 不使用键的 hashCode() 方法,而是直接基于对象的 内存地址 计算哈希值,具体实现为:

/**
 * 计算对象x在哈希表中的索引位置
 * 
 * 与普通HashMap不同,IdentityHashMap使用对象的身份哈希码(System.identityHashCode)
 * 而非对象自身的hashCode()方法,确保基于对象引用而非内容进行哈希计算
 * 
 * @param x 要计算索引的对象
 * @param length 哈希表的长度(必须是2的幂,以保证索引计算的有效性)
 * @return 计算得到的索引位置,范围在[0, length-1]之间
 */
private static int hash(Object x, int length) {
    // 获取对象的身份哈希码,基于对象引用计算,与equals()无关
    int h = System.identityHashCode(x);
    // 哈希扰动计算:通过位移和减法操作增强哈希码的分布性
    // 1. (h << 1) 将哈希码左移1位,相当于乘以2
    // 2. (h << 8) 将哈希码左移8位,相当于乘以256
    // 3. 两者相减等价于 h * (2 - 256) = h * (-254)
    // 4. 与 (length - 1) 按位与,确保结果在哈希表长度范围内(类似取模操作,但更高效)
    // 此计算同时保证结果为偶数,因为哈希表中键值对按"键-值"顺序存储,键的索引必为偶数
    return ((h << 1) - (h << 8)) & (length - 1);
}

注意,System.identityHashCode(k) 方法返回对象的 “身份哈希码”,对于非 null 对象,该值通常与对象的内存地址相关(不受重写的 hashCode() 影响);对于 null 键,固定返回 0。

要理解 ((h << 1) - (h << 8)) & (length - 1) 代码,需从位运算的数学特性和计算逻辑两方面分析:

(1)先看 (h << 1) - (h << 8) 的结果性质

  • h << 1:将整数 h 左移 1 位。左移 1 位的数学意义是 h × 2,结果必然是偶数(任何整数乘以 2 都是偶数)。从二进制角度看,左移 1 位后最低位一定是 0(偶数的二进制特征:二进制最低位为 0 的数,必然是偶数)。

  • h << 8:将整数 h 左移 8 位。同理,左移 8 位等价于 h × 2^8(即 h × 256),结果也是偶数(最低位同样是 0)。

  • 两者相减:(h << 1) - (h << 8)两个偶数相减的结果还是偶数(偶数 - 偶数 = 偶数)。因此,(h << 1) - (h << 8) 的结果一定是偶数,其二进制最低位为 0。

(2)再看与 (length - 1) 按位与的影响

  • IdentityHashMap 的 length(哈希表容量)必须是2 的幂(如 4、8、16、32 等),因此 length - 1 的二进制形式是「连续的 1」(例如:length=32(2^5) 时,length-1=31,32 的二进制为00100000, 31 的二进制就该为 00011111)。

  • 按位与(&)运算的特性是:结果的二进制位,在两个操作数对应位都为 1 时才为 1,否则为 0。

  • 由于 (h << 1) - (h << 8) 的最低位是 0,因此无论 (length - 1) 的最低位是 0 还是 1(实际上 length-1 最低位一定是 1,因为 length 是 2 的幂,length-1 是奇数),两者按位与的结果最低位一定是 0(0 & 1 = 0),而二进制最低位为 0 的数,必然是偶数。所以最终结果一定是偶数。

上面是不是有点复杂,可以换一种思路。如果 length 是 2 的幂,则任何数字对 length 取模运算返回的值位于 [0, length-1],如果 legnth=32,取模最大值为 31(31 的二进制为00011111)。要求某个数 32 的模,不就是保留数的低 5 位(00011111),这不就是与运算。进一步,为了得到偶数,需要将最低位变成 0(不就是左移运算),因为二进制最低位为 0 的数,必然是偶数。

为什么必须是 2 的幂,只有满足该条件,length - 1 才能得到 00011111这种形式的二进制位。

存储(put 方法)

IdentityHashMap 保存数据的逻辑:

  1. 计算键 k 的哈希值和初始索引 i。

  2. 从 i 开始线性探测数组:

    • 若 table[i] 为 null:表示找到空位,存入键 k 到 table[i],值 v 到 table[i+1],返回旧值(若有)。

    • 若 table[i] == k(身份相等):覆盖对应的值 table[i+1],返回旧值。

    • 否则:继续探测下一个位置(i += 2),直到找到空位或相等的键。

  1. 若元素数量达到扩容阈值,触发扩容(容量翻倍,重新哈希所有键值对)。

源码如下:

/**
 * 将指定的键值对放入映射中。
 * 键的相等性基于对象身份(k1 == k2)而非equals()方法,哈希值基于System.identityHashCode()
 * 
 * @param key   要存入的键(允许null)
 * @param value 要关联的值(允许null)
 * @return 与键关联的旧值,若键不存在则返回null
 */
public V put(K key, V value) {
    // 处理null键:将null替换为一个特殊的占位对象(maskNull内部实现),便于统一处理
    final Object k = maskNull(key);

    // 循环重试机制:若发生扩容,需重新计算索引并尝试插入
    retryAfterResize: for (;;) {
        // 获取当前哈希表数组(table是底层存储结构,键值对交替存放:i存键,i+1存值)
        final Object[] tab = table;
        // 当前哈希表长度(必须是2的幂,且为偶数)
        final int len = tab.length;
        // 计算键k在当前哈希表中的初始索引(基于身份哈希值和表长)
        int i = hash(k, len);

        // 线性探测:从初始索引i开始,寻找空位置或相等的键
        // 循环条件:当前位置的键不为null(继续探测)
        for (Object item; (item = tab[i]) != null;
             // 计算下一个键的索引(i += 2,因为键存放在偶数位,值在其后)
             i = nextKeyIndex(i, len)) {
            // 找到相同身份的键(item == k):覆盖旧值并返回
            if (item == k) {
                @SuppressWarnings("unchecked")
                    V oldValue = (V) tab[i + 1]; // 取出旧值(i+1是值的位置)
                tab[i + 1] = value; // 覆盖为新值
                return oldValue; // 返回旧值
            }
        }

        // 未找到相同的键,准备插入新键值对
        // 计算新的元素数量(当前数量+1)
        final int s = size + 1;
        // 检查是否需要扩容:
        // 条件:元素数量的3倍(等效于负载因子0.75的判断:s > len * 0.75 → 3s > len)
        // 若需要扩容,则调用resize()方法(容量翻倍),并通过continue重新尝试插入
        if (s + (s << 1) > len && resize(len))
            continue retryAfterResize;

        // 无需扩容,执行插入操作
        modCount++; // 修改计数器(用于快速失败机制)
        tab[i] = k; // 存放键到索引i
        tab[i + 1] = value; // 存放值到索引i+1
        size = s; // 更新元素数量
        return null; // 无旧值,返回null
    }
}

查询(get 方法)

IdentityHashMap 获取值的逻辑如下:

  1. 计算键 k 的哈希值和初始索引 i。

  2. 从 i 开始线性探测数组:

    • 若 table[i] 为 null:表示无此键,返回 null。

    • 若 table[i] == k:返回对应的值 table[i+1]。

    • 否则:继续探测下一个位置(i += 2)。

/**
 * 根据指定的键获取关联的值。
 * 键的匹配基于对象身份(k1 == k2),而非equals()方法。
 * 
 * @param key 要查询的键(允许null)
 * @return 与键关联的值,若键不存在则返回null
 */
public V get(Object key) {
    // 处理null键:将null替换为内部特殊占位对象,统一后续处理逻辑
    Object k = maskNull(key);
    // 获取当前哈希表数组(键值对交替存储:偶数索引存键,奇数索引存对应值)
    Object[] tab = table;
    // 当前哈希表数组的长度(偶数,且为2的幂)
    int len = tab.length;
    // 计算键k在哈希表中的初始索引(基于身份哈希值和表长)
    int i = hash(k, len);

    // 线性探测查找:从初始索引开始遍历,直到找到匹配的键或空位置
    while (true) {
        // 获取当前索引位置的键(tab[i]为键,tab[i+1]为对应值)
        Object item = tab[i];
        // 找到身份匹配的键(item == k):返回对应的值(i+1位置)
        if (item == k)
            return (V) tab[i + 1];
        // 遇到空位置:说明哈希表中不存在该键,返回null
        if (item == null)
            return null;
        // 移动到下一个键的索引(步长为2,因为键间隔存储)
        i = nextKeyIndex(i, len);
    }
}

删除(remove 方法)

IdentityHashMap 删除逻辑如下:

  1. 找到待删除键 k 的位置 i(逻辑同查询)。

  2. 若找到,将 table[i] 和 table[i+1] 设为 null,然后需要 “重新哈希” 后续冲突的键值对(避免线性探测链断裂):

    • 从 i + 2 开始遍历,对每个非 null 键 k',重新计算其索引并插入(类似 put 逻辑),直到遇到 null 终止。

/**
 * 根据指定的键移除对应的键值对。
 * 键的匹配基于基于对象身份(k1 == k2)匹配,而非equals()方法。
 * 
 * @param key 要移除的键(允许null)
 * @return 被移除的键关联的值,若键不存在则返回null
 */
public V remove(Object key) {
    // 处理null键:将null替换为内部特殊占位对象,统一后续处理逻辑
    Object k = maskNull(key);
    // 获取当前哈希表数组(键值对交替存储:偶数索引存键,奇数索引存对应值)
    Object[] tab = table;
    // 当前哈希表数组的长度(偶数,且为2的幂)
    int len = tab.length;
    // 计算键k在哈希表中的初始索引(基于身份哈希值和表长)
    int i = hash(k, len);

    // 线性探测查找目标键
    while (true) {
        // 获取当前索引位置的键(tab[i]为键,tab[i+1]为对应值)
        Object item = tab[i];
        // 找到身份匹配的键(item == k):执行移除操作
        if (item == k) {
            modCount++; // 修改计数器(用于快速失败机制)
            size--; // 减少元素数量
            
            // 保存并返回被移除的值(i+1位置为值)
            @SuppressWarnings("unchecked")
            V oldValue = (V) tab[i + 1];
            
            // 清空当前键值对的存储位置
            tab[i + 1] = null; // 清除值
            tab[i] = null; // 清除键
            
            // 处理删除后的哈希表调整:重新哈希后续冲突的键值对,避免探测链断裂
            closeDeletion(i);
            
            return oldValue; // 返回被移除的值
        }
        // 遇到空位置:说明哈希表中不存在该键,返回null
        if (item == null)
            return null;
        // 移动到下一个键的索引(步长为2,因为键间隔存储)
        i = nextKeyIndex(i, len);
    }
}

冲突处理逻辑

首先需要明确,IdentityHashMap 的底层数组 table 中,键值对按「键存于偶数索引,值存于奇数索引」的规则存储(例如:键在 i=0,值在 i=1;键在 i=2,值在 i=3,以此类推)。因此,所有键的索引必须是偶数,计算哈希时也会保证结果为偶数(如之前分析的 hash 方法返回偶数)。

假设 IdentityHashMap 的容量为 32,要保存数据的键的初始索引为 30(偶数,对应值在 31),且该位置已被占用。

此时,IdentityHashMap 会线性探测寻找下一个空位置。具体逻辑如下:

当键的初始索引(如 30)已被占用(即 table[30] 不为 null 且与当前键不相等),IdentityHashMap 会通过 nextKeyIndex 方法计算下一个候选索引,步骤如下:

// 计算下一个键的索引(步长为2,保证索引始终为偶数)
private static int nextKeyIndex(int i, int len) {
    return (i + 2 < len) ? i + 2 : 0; // 超过数组长度则循环到开头
}

注意:每次探测下一个偶数索引(如 30 → 32,但容量 32 的数组最大索引为 31,因此 32 会循环到 0)。当索引超过数组长度时,会从 0 开始重新探测(形成环形结构)。

  

IdentityHashMap 适用场景

下面是 IdentityHashMap 三个适用场景:

(1) 对象身份映射:需要严格区分对象引用(而非内容)的场景,例如跟踪对象实例的元数据。

(2)序列化 / 克隆场景:在实现对象深度克隆或序列化时,用于记录已处理的对象以避免循环引用。

(3)临时缓存:需要基于对象身份缓存临时数据,且不希望因对象内容相等而冲突。

注意,IdentityHashMap 通常不是常规场景的首选,只有在明确需要基于引用相等性判断键时才考虑使用,否则应优先选择 HashMap 或其他 Map 实现。

更多信息参考 https://docs.oracle.com/javase/8/docs/api/java/util/IdentityHashMap.html API 文档。

  

说说我的看法
全部评论(
没有评论
关于
本网站专注于 Java、数据库(MySQL、Oracle)、Linux、软件架构及大数据等多领域技术知识分享。涵盖丰富的原创与精选技术文章,助力技术传播与交流。无论是技术新手渴望入门,还是资深开发者寻求进阶,这里都能为您提供深度见解与实用经验,让复杂编码变得轻松易懂,携手共赴技术提升新高度。如有侵权,请来信告知:hxstrive@outlook.com
其他应用
公众号