我们都知道Map的一大特性是key唯一不可重复,可是真的是这样的吗?
我们来试验一下:
运行结果:
我们可以看到在map里有两个同样的person作为key,打破了map的key不可重复的特性。
我们平时操作map一般不会出现这样的结果,怎样操作会出现上述的现象呢?
1、首先有前提条件,作为key的person必须重写hashCode与equals这两个方法保证我们在改变person的属性之后,该person的hash值发生变化。
2、其次是我们在map中put一个以person对象作为key的元素,然后我们修改该person对象的某一个属性,再次把该person对象作为key值put到map中,就得到了上述结果。
为什么会出现上述的问题呢?
我们要明白map的数据结构以及数据是如何存储到map中的
JDK1.7 HashMap是数组+链表的结构
JDK8之后HashMap是数组+链表+红黑树的结构
tips:当然这里我们就不过多的讨论7与8结构的区别
我们在put一个元素的时候,(其余逻辑省略,这里只关注元素如何定位到数组的桶位置),先拿到key对象的hash值h1,h1无符号右移16位得到h2,
再把h1与h2进行异或运算得到h3,h3与数组的(length-1)进行与运算得到元素在数组上的最终位置
tips:如果数组长度较小的时候(大多数情况下map的长度不大),key产生的hash值如果高位变化较大很大,而地位变化很小时,
如果直接拿key的hash值与上(length-1)很容易产生hash冲突,所以无符号右移16位在异或低16位使得高混乱区域与低混乱区域做一个中和,提高hash高低位的一个随机性,减少hash冲突
上面我们讲述了map是如何把元素放入到数组中的,我们再回到上面的问题,第一次把person作为key放入map之后,修改了person的name属性之后,person的hash值发生变化,从而计算出的
桶位置也随之而改变(大概率会改变,不是绝对的)再次put到map中就得到两个相同key值的map。
那么在生产应用中我们要避免使用类似于person这样的对象作为key值存储在Map中,可以使用Integer、String这样一些不可变的对象来作为key就可以避免上述情况的发生。
插一句题外话,HashSet是无序不可重复的,它其实也存在上面的情况,原因很简单HashSet的底层就是HashMap
附HashSet相关代码截图