【小家java】Java中IdentityHashMap使用详解---允许key重复(阐述和HashMap的区别)

版权声明: https://blog.csdn.net/f641385712/article/details/81880711

相关阅读

【小家java】java5新特性(简述十大新特性) 重要一跃
【小家java】java6新特性(简述十大新特性) 鸡肋升级
【小家java】java7新特性(简述八大新特性) 不温不火
【小家java】java8新特性(简述十大新特性) 饱受赞誉
【小家java】java9新特性(简述十大新特性) 褒贬不一
【小家java】java10新特性(简述十大新特性) 小步迭代


应该有很多人不知道IdentityHashMap的存在,其中不乏工作很多年的Java开发者,会有很多人以为这是第三方jar包,实际上它是Jdk源码自带的集合类。

本文主要讲解IdentityHashMap的使用和他的一些特性。很多场景上使用它,会让你事半功倍。

对Map的认识

其实我们对Map都有一个通用认知:只要key相同,就不能重复往里面put,但是你真的了解“相同”这两个字吗?看下面这个例子吧:

 public static void main(String[] args) {
        Map<String, String> map = new HashMap<>();
        map.put("a", "1");
        map.put("a", "2");
        map.put("a", "3");
        System.out.println(map.size()); //1

        Map<String, String> hashMap = new HashMap<>();
        hashMap.put(new String("a"), "1");
        hashMap.put(new String("a"), "2");
        hashMap.put(new String("a"), "3");
        System.out.println(hashMap.size()); //1

        Map<Integer, String> hashMap2 = new HashMap<>();
        hashMap2.put(new Integer(200), "1");
        hashMap2.put(new Integer(200), "2");
        hashMap2.put(new Integer(200), "3");
        System.out.println(hashMap2.size()); //1

        Map<Demo, String> hashMap3 = new HashMap<>();
        hashMap3.put(new Demo(1), "1");
        hashMap3.put(new Demo(1), "2");
        hashMap3.put(new Demo(1), "3");
        System.out.println(hashMap3.size()); //3
    }

从结果中,你是否感觉到了惊讶?
如果是:那证明你还不是真的了解HashMap
如果不是:那你对底层的了解还是比较透彻的

不管怎么样,我给出下面两段源码,给与解释:
containsKey和get的源码:

public boolean containsKey(Object key) {
        return getNode(hash(key), key) != null;
    }
public V get(Object key) {
        Node<K,V> e;
        return (e = getNode(hash(key), key)) == null ? null : e.value;
    }

我们发现,它底层其实都调用了一个getNode方法,关键在于key上面的hash方法,因此我们主要看看这个hash方法:

static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

发现,最终的返回值取决于key.hashCode()方法。好了,问题的答案已经若影若现了。我们发现key是否相同,取决于HashCode是否相等。

可能有人对上面的还有输出结构还有疑问:我的key明明是new出来的,为什么size还是成为了1呢????

这里我贴出两段源码,大家应该就能明白了:
String和Interger的HashCode方法源码:

public int hashCode() {
        int h = hash;
        if (h == 0 && value.length > 0) {
            hash = h = isLatin1() ? StringLatin1.hashCode(value)
                                  : StringUTF16.hashCode(value);
        }
        return h;
    }
@Override
    public int hashCode() {
        return Integer.hashCode(value);
    }

我们发现他俩都重写了此方法,并且只和value值有关。因此只要内容相同,他们的hashCode就是相等的,这就是为什么平时我们都乐意用它们来作键而不会出问题的最根本原因。

扫描二维码关注公众号,回复: 2899105 查看本文章

而普通对象使用的父类Object的HashCode方法,是个native方法,与地址值有关,因此new出来的对象肯定不是同一个key了。

Key的比较,和equals有关吗?

针对于上面的例子,我们重写Demo类的equals方法如下:

@Override
        public boolean equals(Object obj) {
            return true;
        }

再执行:

 Map<Demo, String> hashMap3 = new HashMap<>();
        hashMap3.put(new Demo(1), "1");
        hashMap3.put(new Demo(1), "2");
        hashMap3.put(new Demo(1), "3");
        System.out.println(hashMap3.size()); //3

我们发现输出的结果还是3。
我们再重写HashCode方法:

@Override
        public boolean equals(Object obj) {
            return true;
        }

        @Override
        public int hashCode() {
            return id;
        }

继续运行上面的方法。结果这次肯定在我们意料之中—–输出的结果为1。
那么我们控制变量法,去掉equals的重写,只保留hashCode试试:

 @Override
        public int hashCode() {
            return id;
        }

输出结果:3

所以我们得出结论:

HashMap等Map的key的比较,比较的是HashCode值,和equals无关

IdentityHashMap

顾名思义,它允许”自己”相同的key保存进来,因此又一个相同二字。直接看例子

   public static void main(String[] args) {

        //IdentityHashMap使用===================================
        Map<String, String> identityHashMap = new IdentityHashMap<>();
        identityHashMap.put(new String("a"), "1");
        identityHashMap.put(new String("a"), "2");
        identityHashMap.put(new String("a"), "3");
        System.out.println(identityHashMap.size()); //3

        Map<Demo, String> identityHashMap2 = new IdentityHashMap<>();
        identityHashMap2.put(new Demo(1), "1");
        identityHashMap2.put(new Demo(1), "2");
        identityHashMap2.put(new Demo(1), "3");
        System.out.println(identityHashMap2.size()); //3

    }

备注,此时的Demo类没有复写任何方法。从结果我们可以看出,它好像违背了Map的规则,把相同的key保存进去了。
是的,这就是它最大的特性之一。因此对应的,我们看看get方法结果

System.out.println(identityHashMap.get("a")); //null
System.out.println(identityHashMap2.get(new Demo(1))); //null

得到的是两个大大的null。那么我们继续针对于Demo类,重写eq和hashCode方法如下:

 private static class Demo {
        private Integer id;

        public Demo(Integer id) {
            this.id = id;
        }

        @Override
        public boolean equals(Object obj) {
            return true;
        }

        @Override
        public int hashCode() {
            return id;
        }
    }

重新执行get方法,我们发现,得到的还是null。所以它竟然与eq和HashCode方法都木有关系哟。为了解释这个问题,我插播一个小例子:

Java中==,到底比较的什么?

 public static void main(String[] args) {

        Demo demo1 = new Demo(1);
        Demo demo2 = new Demo(1);
        System.out.println(demo1 == demo2); //false
        System.out.println(demo1.hashCode()); //1
        System.out.println(demo2.hashCode()); //1
        System.out.println(System.identityHashCode(demo1)); //998351292
        System.out.println(System.identityHashCode(demo2)); //1684106402

    }

从这个例子中,我们能够得出结论:

==比较的是地址值,而不是HashCode,所以这里以后千万不要掉进误区了。

而我们的IdentityHashMap,比较key值,直接使用的是==,因此上面例子出现的结果,我们自然而然的就能够理解了。so,下面这句输出:

   public static void main(String[] args) {

        Demo demo1 = new Demo(1);
        Demo demo2 = new Demo(1);
        Map<Demo, String> identityHashMap = new IdentityHashMap<>();
        identityHashMap.put(demo1,"demo1");
        identityHashMap.put(demo2,"demo2");
        System.out.println(identityHashMap.get(demo1)); //demo1

    }

不再输出null,而是能够get到值了。

最后

  1. 比如对于要保存的key,k1和k2,当且仅当k1== k2的时候,IdentityHashMap才会相等,而对于HashMap来说,相等的条件则是:对比两个key的hashCode等
  2. IdentityHashMap**不是Map的通用实现**,它有意违反了Map的常规协定。并且IdentityHashMap允许key和value都为null。
  3. 同HashMap,IdentityHashMap也是无序的,并且该类不是线程安全的,如果要使之线程安全,可以调用Collections.synchronizedMap(new IdentityHashMap(…))方法来实现。

注意:

  • IdentityHashMap重写了equals和hashcode方法,不过需要注意的是hashCode方法并不是借助Object的hashCode来实现的,而是通过System.identityHashCode方法来实现的。
  • hashCode的生成是与key和value都有关系的,这就间接保证了key和value这对数据具备了唯一的hash值。同时通过重写equals方法,判定只有key值全等情况下才会判断key值相等。这就是IdentityHashMap与普通HashMap不同的关键所在。

猜你喜欢

转载自blog.csdn.net/f641385712/article/details/81880711