HashMap 和 IdentityHashMap 的区别

1、HashMap 和 IdentityHashMap 举例对比

public static void main(String[] args) {
    HashMap hashMap = new HashMap();
    hashMap.put("1", "1");
    hashMap.put(String.valueOf(1), "1");
    hashMap.put(String.valueOf('1'), "1");
    hashMap.put(new String("1"), "1");
    System.out.println(hashMap);

    IdentityHashMap identityHashMap = new IdentityHashMap();
    identityHashMap.put("1", "1");
    identityHashMap.put(String.valueOf(1), "1");
    identityHashMap.put(String.valueOf('1'), "1");
    identityHashMap.put(new String("1"), "1");
    System.out.println(identityHashMap);
}

控制台打印:

{1=1}
{1=1, 1=1, 1=1, 1=1}

上面代码中 put 的 4 个 key:“1”,String.value(1)String.value('1')new String("1"),它们指向的内存中的不同的地址空间,是 4 个不同对象,使用 == 进行判等,两两互不相等;

而 String 类重写了 hashCode 和 equals 方法,这4个对象 hashCode 都返回相同的值,两两执行 equals 方法,返回的都是 true

当 HashMap 对 4 个 key 执行 put 操作时,使用 hashCode 作为 hash 值,使用 equals 进行相等判断,后 3 个 put 操作,最终执行的是更新操作,最后 HashMap 中只有 1 项;

IdentityHashMap 对 4 个 key 执行 put 操作时,使用 System.identityHashCode 作为 hash 值,使用 == 进行相等判断,后 3 个 put 操作,最终执行的是插入操作,最后 IdentityHashMap 中有 4 项。

以上就是 HashMap 和 IdentityHashMap 在功能上最本质的差别。


2、HashMap 和 IdentityHashMap

HashMapIdentityHashMap
存储结构不同table 中的每一个项都是一个 Node 结构,包含 key 和 value 的值,冲突发生时对应的项变成链表或红黑树结构;table 中相邻的两个数据为一组 key-value 对:索引为偶数的位置存储的是 key,与 key 右相邻的位置存储的是 value;
使用的Hash值不同使用 hashCode 方法返回值使用 System.identityHashCode 返回值
判等方法不同使用 equeal 进行判等使用 == 判等
处理Hash冲突方式不同链表 + 红黑树开发地址法中的线型探测

3、IdentityHashMap 源码分析

IdentityHashMapHashMap 都是实现了 Hash 表这种数据结构,下面主要将两者不同的地方拿来分析。

3.1 hash 定位索引 index
private static int hash(Object x, int length) {
    int h = System.identityHashCode(x);
    // Multiply by -127, and left-shift to use least bit as part of hash
    return ((h << 1) - (h << 8)) & (length - 1);
}

调用的是 System.identityHashCode 得到 hash 值,然后以此为基础去计算在 table 中的索引位置 index。

3.2 get 查找
public V get(Object key) {
    Object k = maskNull(key);
    Object[] tab = table;
    int len = tab.length;
    int i = hash(k, len);
    while (true) {
        Object item = tab[i];
        if (item == k)
            return (V) tab[i + 1];
        if (item == null)
            return null;
        i = nextKeyIndex(i, len);
    }
}

IdentityHashMap 的 get 方法比 HashMap 的 get 方法简单很多,一种只用3种情况:

  1. item == k,找到了对应的 key,将相应的 value 返回,value 存在 key 右相邻的位置,返回 tab[i + 1]

  2. item == null,根据 hash 定位到 item 为空,表示当前的 key 在 table 中不存在,直接返回 null

  3. item != null && item != key,表示 hash 冲突发生,调用 nextKeyIndex 获取处理冲突后的 index 位置,然后重复上面的过程;

需要注意的是这里的判断是否相等操作使用的 ==,而不是 equals 方法;

3.3 nextKeyIndex 处理 hash 冲突
private static int nextKeyIndex(int i, int len) {
    return (i + 2 < len ? i + 2 : 0);
}

IdentityHashMap 处理冲突的方式是开发地址法中的线型再探测,当前的位置别占用后,就在右相邻的位置去找,而 IdentityHashMap 中一个 key-value 键值对,占用 table 的两个位置,所以这里的操作是加 2,如果超出 table 大小,再从 0 开始。

3.4. put 方法
public V put(K key, V value) {
    final Object k = maskNull(key);

    retryAfterResize: for (;;) {
        final Object[] tab = table;
        final int len = tab.length;
        int i = hash(k, len);

        for (Object item; (item = tab[i]) != null;
             i = nextKeyIndex(i, len)) {
            if (item == k) {
                @SuppressWarnings("unchecked")
                V oldValue = (V) tab[i + 1];
                tab[i + 1] = value;
                return oldValue;
            }
        }

        final int s = size + 1;
        // Use optimized form of 3 * s.
        // Next capacity is len, 2 * current capacity.
        if (s + (s << 1) > len && resize(len))
            continue retryAfterResize;

        modCount++;
        tab[i] = k;
        tab[i + 1] = value;
        size = s;
        return null;
    }
}

IdentityHashMap 的 put 方法也非常简单,调用 hash 方法,获取 key 在 table 的位置 index,然后进行赋值操作,也是分成了 3 种情况:

  1. item == k,找到了对应的 key,value 存在 key 右相邻的位置,对 tab[i + 1] 进行更新,并返回原来的值;

  2. item == null,表示 table 中没有对应的 key 值,跳出 for 循环,执行 tab[i] = ktab[i + 1] = value 进行新 key 的插入操作。个人觉得这里的扩容时机选择的不太好,好不容易找到的更新位置,因为扩容给整没了,还得再次重新计算,可以和 HashMap 一样,在更新后再扩容;

  3. item != null && item != key,表示 hash 冲突发生,调用 nextKeyIndex 获取处理冲突后的 index 位置,然后重复上面的过程。

4、IdentityHashMap 使用场景

当 Hash 存储结构 Key 的类型,没有重写 hashCode 和 equals 方法时,并且 Hash 结构中存储的数据较少, Hash 冲突不严重的时候,就可以使用 IdentityHashMap 替换 HashMap;

JDK 动态代理实现中,就使用了 IdentityHashMap,作用是对传入的代理接口进行重复性验证,代码在 Proxy.ProxyClassFactory.apply 方法中,部分代码如下:

@Override
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {

    Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
    for (Class<?> intf : interfaces) {
        /*
         * Verify that the class loader resolves the name of this
         * interface to the same Class object.
         */
        Class<?> interfaceClass = null;
        try {
            interfaceClass = Class.forName(intf.getName(), false, loader);
        } catch (ClassNotFoundException e) {
        }
        if (interfaceClass != intf) {
            throw new IllegalArgumentException(
                    intf + " is not visible from class loader");
        }
        /*
         * Verify that the Class object actually represents an
         * interface.
         */
        if (!interfaceClass.isInterface()) {
            throw new IllegalArgumentException(
                    interfaceClass.getName() + " is not an interface");
        }
        /*
         * Verify that this interface is not a duplicate.
         */
        if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
            throw new IllegalArgumentException(
                    "repeated interface: " + interfaceClass.getName());
        }
    }

    //......
}

这里 interfaceSetIdentityHashMap 类型,用来检查传入 interfaces 数组里面是不是有重复的项;

其中 Key 是 Class 类型,而 Class 类没有重写 hashCode 和 equals 方法,所以把 IdentityHashMap 替换为 HashMap 功能上没有任何问题;

但是这里 IdentityHashMap 的效果更好,因为动态代理出入的接口的个数非常少,产生冲突的概率非常小,结构更简单的 IdentityHashMap 在此场景下,就更加适用。


转载:https://blog.csdn.net/Mssyaa/article/details/121792691

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值