天天看点

对于String作为 HashMap key 的一些思考。

一切都是突发奇想。

先说我的结论:String作为key的时候,是放入对象,对象改变会造成影响。(感觉像废话,但是结论是需要证明出来的)

首先要先补充一些知识点:

  1. String 底层是用 final 关键字,理论上来说是不可能更改的,如果你将引用指向新的字符串,会创建一个新的 String 对象。
  2. String 底层其实是维护一个 byte 数组,我们知道,对于 final 修饰的数组,虽然地址不可改变,但是数组值是可以改变的。
  3. 理论上,反射可以破坏除了枚举的一切东西。
  4. HashMap 对于新key的插入,会有一个判重的操作,如果重复,会覆盖。

 这篇文章主要是思考,主要是源于其他大佬博客的一句话:

“因为 String 的 hash 值经常被使用,例如 String 用做 HashMap 的 key。不可变的特性可以使得 hash 值也不可变,因此只需要进行一次计算。”

这句话没有什么问题,但是我在想,当一个 String 对象当成key之后,由于他的不可变特性如何去验证他。

HashMap<String,Object> map = new HashMap<>();

        String a = "a";

        map.put(a,1);

        map.put("b",2);

        System.out.println(map.toString());

        a  = "c";

        map.put(a,3);

        System.out.println(map);
           

首先到这里应该都很好懂,输出应该是:

{a=1, b=2}
{a=1, b=2, c=3}
           

接着利用反射去更改 a 的值,使他变成字符串 “a”;

Field array=a.getClass().getDeclaredField("value");
        array.setAccessible(true);
        byte[] arr=(byte[])array.get(a);
        arr[0]='a';

        System.out.println(map);
           

得到结果

{a=1, b=2, a=3}
           

这时候,有些朋友可能就有点发蒙了,不说会覆盖吗。

那当我分别去get()的时候,会获取以下结果。

get("a") ==> 1

get("b") ==> 2

get(a) ==> 3

get("c") ==> 3
           

 理由很简单,其实对象a用的是字符串“c”的地址,而hashcode又是依靠地址计算的,也就是说对象a的地址并没有改变,只是值变化了而已,而“c”已经创建过,在常量池有一个地址,“c”的地址与现在的对象 a 地址相同,hashcode也相同,所以后两个表达式的输出结果相同。

还可以扩展:

String lk = "c";

map.put(lk,"4");

System.out.println(map.toString());
           
输出:{a=1, b=2, a=4}
           

上面说了,其实“c”已经创建过,在常量池有一个地址,将一个新的引用 lk 指向他之后,其实地址是相同的,hashcode也是相同的,put 的时候,只会去覆盖他的值,但是key的样子依然不会变。

啊,又是增涨奇怪知识的一天。